1. 程式人生 > >QT 訊號 槽(emit,signal,slot)

QT 訊號 槽(emit,signal,slot)

 

Qt中的類庫有接近一半是從基類QObject上繼承下來,訊號與反應槽(signals/slot)機制就是用來在QObject類或其子類間通訊的方法。作為一種通用的處理機制,訊號與反應槽非常靈活,可以攜帶任意數量的引數,引數的型別也由使用者自定。同時其本身也是型別安全的,任何一個從QObject或其子類繼承的使用者類都可以使用訊號與反應槽。

  在Qt中,對於發出訊號的物件來說,它並不知道是誰接收了這個訊號。這樣的設計可能在某些地方會有些不便,但卻杜絕了緊耦合,於總體設計有利。反應槽是用來接收訊號的, 但它實際上也是普通的函式,程式設計師可以象呼叫普通函式一樣來呼叫反應槽。與訊號類似的是,反應槽的擁有者也不知道是誰向它發出了訊號

。在程式設計過程中,多個訊號可以連線至一個反應槽,類似的,一個訊號也可以連線至多個反應槽,甚至一個訊號可以連線至另一個訊號。

    在Windows中,如果我們需要多個選單都激發一個函式,一般是先寫一個共用函式,然後在每個選單的事件中呼叫此函式。在Qt中如果要實現同樣的功能,就可以把實現部分寫在一個選單中,然後把其他選單與這個選單級聯起來。

    我們先來看一個簡單的樣例:

 

   class Demo : public QObject
          {
              Q_OBJECT
 
          public:
              Demo();
              int value() const { return val; };
 
          public slots:
              void setValue( int );
          
          signals:
              void valueChanged( int );
          
          private:
              int val;
          };
          
    由樣例可看到,類的定義中有兩個關鍵字slots和signals,還有一個巨集Q_OBJECT。
在Qt的程式中如果使用了訊號與反應槽就必須在類的定義中宣告這個巨集,不過如果你聲明瞭該巨集但在程式中並沒有訊號與反應槽,
對程式也不會有任何影響,所以建議大家在用Qt寫程式時不妨都把這個巨集加上。使用slots定義的就是訊號的功能實現,即反應槽,例如:
 
          void Demo::setValue( int v )
          {
               if ( v != val )
               {
                   val = v;
                   emit valueChanged(v);
               }
          }
          
    這段程式表明當setValue執行時它將釋放出valueChanged這個訊號。
    以下程式示範了不同物件間訊號與反應槽的連線。
 
          Demo a, b;
          
          connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
          
          b.setValue( 11 );
          
          a.setValue( 79 );
          
          b.value(); // b的值將是79而不是原先設的11

 

 

    在以上程式中,一旦訊號與反應槽連線,當執行a.setValue(79)時就會釋放出一個valueChanged(int)的訊號,物件b將會收到這個訊號並觸發setValue(int)這個函式。當b在執行setValue(int)這個函式時,它也將釋放valueChanged(int)這個訊號,當然b的訊號無人接收,因此就什麼也沒幹。

請注意,在樣例中我們僅當輸入變數v不等於val時才釋放訊號,因此就算對a與b進行了交叉連線也不會導致死迴圈的發生。由於在樣例中使用了Qt特有的關鍵字和巨集,而Qt本身並不包括C++的編譯器,因此如果用流行的編譯程式(如Windows下的Visual C++或Linux下的gcc)是不能直接編譯這段程式碼的,必須用Qt的中間編譯工具moc.exe把該段程式碼轉換為無專用關鍵字和巨集的C++程式碼才能為這些編譯程式所解析、編譯與連結。

    以上程式碼中訊號與反應槽的定義是在類中實現的。那麼,非類成員的函式,比如說一個全域性函式可不可以也這樣做呢?答案是不行,只有是自身定義了訊號的類或其子類才可以發出該種訊號。一個物件的不同訊號可以連線至不同的物件。當一個訊號被釋放時,與之連線的反應槽將被立刻執行,就象是在程式中直接呼叫該函式一樣。訊號的釋放過程是阻塞的,這意味著只有當反應槽執行完畢後該訊號釋放過程才返回。如果一個訊號與多個反應槽連線,則這些反應槽將被順序執行,排序過程則是任意的。因此如果程式中對這些反應槽的先後執行次序有嚴格要求的,應特別注意。使用訊號時還應注意:訊號的定義過程是在類的定義過程即標頭檔案中實現的。為了中間編譯工具moc的正常執行,不要在原始檔(.cpp)中定義訊號,同時訊號本身不應返回任何資料型別,即是空值(void)。如果你要設計一個通用的類或控制元件,則在訊號或反應槽的引數中應儘可能使用常規資料以增加通用性。如上例程式碼中valueChanged的引數為int型,如果它使用了特殊型別如QRangeControl::Range,那麼這種訊號只能與RangeControl中的反應槽連線。如前所述,反應槽也是常規函式,與未定義slots的使用者函式在執行上沒有任何區別。

    但在程式中不可把訊號與常規函式連線在一起,否則訊號的釋放不會引起對應函式的執行。要命的是中間編譯程式moc並不會對此種情況報錯,C++編譯程式更不會報錯。初學者比較容易忽略這一點,往往是程式編好了沒有錯誤,邏輯上也正確,但執行時就是不按自己的意願出現結果,這時候應檢查一下是不是這方面的疏忽。

    Qt的設計者之所以要這樣做估計是為了訊號與反應槽之間匹配的嚴格性。既然反應槽與常規函式在執行時沒有什麼區別,因此它也可以定義成公共反應槽(public slots)、保護反應槽(protected slots)和私有反應槽(private slots)。如果需要,我們也可以把反應槽定義成虛擬函式以便子類進行不同的實現,這一點是非常有用的。