Linux下應用程式開發:QT的訊號與槽機制(轉)

BSDLite發表於2007-08-12
Linux下應用程式開發:QT的訊號與槽機制(轉)[@more@]  訊號與槽作為QT的核心機制在QT程式設計中有著廣泛的應用,本文介紹了訊號與槽的一些基本概念、元物件工具以及在實際使用過程中應注意的一些問題。
  
  QT是一個跨平臺的C++ GUI應用構架,它提供了豐富的視窗部件集,具有物件導向、易於擴充套件、真正的元件程式設計等特點,更為引人注目的是目前Linux上最為流行的KDE桌面環境就是建立在QT庫的基礎之上。QT支援下列平臺:MS/WINDOWS-95、98、NT和2000;UNIX/X11-Linux、Sun Solaris、HP-UX、Digital Unix、IBM AIX、SGI IRIX;EMBEDDED-支援framebuffer的Linux平臺。伴隨著KDE的快速發展和普及,QT很可能成為Linux視窗平臺上進行軟體開發時的GUI首選。
  
  一、概述
  訊號和槽機制是QT的核心機制,要精通QT程式設計就必須對訊號和槽有所瞭解。訊號和槽是一種高階介面,應用於物件之間的通訊,它是QT的核心特性,也是QT區別於其它工具包的重要地方。訊號和槽是QT自行定義的一種通訊機制,它獨立於標準的C/C++語言,因此要正確的處理訊號和槽,必須藉助一個稱為moc(Meta Object Compiler)的QT工具,該工具是一個C++預處理程式,它為高層次的事件處理自動生成所需要的附加程式碼。
  
  在我們所熟知的很多GUI工具包中,視窗小部件(widget)都有一個回撥函式用於響應它們能觸發的每個動作,這個回撥函式通常是一個指向某個函式的指標。但是,在QT中訊號和槽取代了這些凌亂的函式指標,使得我們編寫這些通訊程式更為簡潔明瞭。 訊號和槽能攜帶任意數量和任意型別的引數,他們是型別完全安全的,不會像回撥函式那樣產生core dumps。
  
  所有從QObject或其子類(例如Qwidget)派生的類都能夠包含訊號和槽。當物件改變其狀態時,訊號就由該物件發射(emit)出去,這就是物件所要做的全部事情,它不知道另一端是誰在接收這個訊號。這就是真正的資訊封裝,它確保物件被當作一個真正的軟體元件來使用。槽用於接收訊號,但它們是普通的物件成員函式。一個槽並不知道是否有任何訊號與自己相連線。而且,物件並不瞭解具體的通訊機制。
  
  你可以將很多訊號與單個的槽進行連線,也可以將單個的訊號與很多的槽進行連線,甚至於將一個訊號與另外一個訊號相連線也是可能的,這時無論第一個訊號什麼時候發射系統都將立刻發射第二個訊號。總之,訊號與槽構造了一個強大的部件程式設計機制。
  
  二、訊號
  當某個訊號對其客戶或所有者發生的內部狀態發生改變,訊號被一個物件發射。只有 定義過這個訊號的類及其派生類能夠發射這個訊號。當一個訊號被髮射時,與其相關聯的槽將被立刻執行,就象一個正常的函式呼叫一樣。訊號-槽機制完全獨立於任何GUI事件迴圈。只有當所有的槽返回以後發射函式(emit)才返回。 如果存在多個槽與某個訊號相關聯,那麼,當這個訊號被髮射時,這些槽將會一個接一個地 執行,但是它們執行的順序將會是隨機的、不確定的,我們不能人為地指定哪個先執行、哪 個後執行。
  
  訊號的宣告是在標頭檔案中進行的,QT的signals關鍵字指出進入了訊號宣告區,隨後即可 宣告自己的訊號。例如,下面定義了三個訊號:
  
   signals:
   void mySignal();
   void mySignal(int x);
   void mySignalParam(int x,int y);
  
  
  在上面的定義中,signals是QT的關鍵字,而非C/C++的。接下來的一行void mySignal() 定義了訊號mySignal,這個訊號沒有攜帶引數;接下來的一行void mySignal(int x)定義 了重名訊號mySignal,但是它攜帶一個整形引數,這有點類似於C++中的虛擬函式。從形式上 講訊號的宣告與普通的C++函式是一樣的,但是訊號卻沒有函式體定義,另外,訊號的返回 型別都是void,不要指望能從訊號返回什麼有用資訊。
  
  訊號由moc自動產生,它們不應該在.cpp檔案中實現。
  
  三、槽
  槽是普通的C++成員函式,可以被正常呼叫,它們唯一的特殊性就是很多訊號可以與其相關聯。當與其關聯的訊號被髮射時,這個槽就會被呼叫。槽可以有引數,但槽的引數不能有預設值。
  
  既然槽是普通的成員函式,因此與其它的函式一樣,它們也有存取許可權。槽的存取許可權決定了誰能夠與其相關聯。同普通的C++成員函式一樣,槽函式也分為三種型別,即public slots、private slots和protected slots。
  
  public slots:在這個區內宣告的槽意味著任何物件都可將訊號與之相連線。這對於元件程式設計非常有用,你可以建立彼此互不瞭解的物件,將它們的訊號與槽進行連線以便資訊能夠正確的傳遞。
  protected slots:在這個區內宣告的槽意味著當前類及其子類可以將訊號與之相連線。這適用於那些槽,它們是類實現的一部分,但是其介面介面卻面向外部。
  private slots:在這個區內宣告的槽意味著只有類自己可以將訊號與之相連線。這適用於聯絡非常緊密的類。
  槽也能夠宣告為虛擬函式,這也是非常有用的。
  
  槽的宣告也是在標頭檔案中進行的。例如,下面宣告瞭三個槽:
  
   public slots:
   void mySlot();
   void mySlot(int x);
   void mySignalParam(int x,int y);
  
  四、訊號與槽的關聯
  透過呼叫QObject物件的connect函式來將某個物件的訊號與另外一個物件的槽函式相關聯,這樣當發射者發射訊號時,接收者的槽函式將被呼叫。該函式的定義如下:
  
   bool QObject::connect ( const QObject * sender, const char * signal,
   const QObject * receiver, const char * member ) [static]
  
  這個函式的作用就是將發射者sender物件中的訊號signal與接收者receiver中的member槽函式聯絡起來。當指定訊號signal時必須使用QT的宏SIGNAL(),當指定槽函式時必須使用宏SLOT()。如果發射者與接收者屬於同一個物件的話,那麼在connect呼叫中接收者引數可以省略。
  
  例如,下面定義了兩個物件:標籤物件label和捲軸物件scroll,並將valueChanged()訊號與標籤物件的setNum()相關聯,另外訊號還攜帶了一個整形引數,這樣標籤總是顯示捲軸所處位置的值。
  
   QLabel   *label = new QLabel;
    QScrollBar *scroll = new QScrollBar;
    QObject::connect( scroll, SIGNAL(valueChanged(int)),
             label, SLOT(setNum(int)) );
  
  
  一個訊號甚至能夠與另一個訊號相關聯,看下面的例子:
  
   class MyWidget : public QWidget
    {
    public:
      MyWidget();
    ...
    signals:
      void aSignal();
    ...
    private:
    ...
      QPushButton *aButton;
    };
  
    MyWidget::MyWidget()
    {
      aButton = new QPushButton( this );
      connect( aButton, SIGNAL(clicked()), SIGNAL(aSignal()) );
    }
  
  在上面的建構函式中,MyWidget建立了一個私有的按鈕aButton,按鈕的單擊事件產生的訊號clicked()與另外一個訊號aSignal()進行了關聯。這樣一來,當訊號clicked()被髮射時,訊號aSignal()也接著被髮射。當然,你也可以直接將單擊事件與某個私有的槽函式相關聯,然後在槽中發射aSignal()訊號,這樣的話似乎有點多餘。
  
  當訊號與槽沒有必要繼續保持關聯時,我們可以使用disconnect函式來斷開連線。其定義如下:
  
   bool QObject::disconnect ( const QObject * sender, const char * signal,
   const Object * receiver, const char * member ) [static]
  
  這個函式斷開發射者中的訊號與接收者中的槽函式之間的關聯。
  
  有三種情況必須使用disconnect()函式:
  
  斷開與某個物件相關聯的任何物件。這似乎有點不可理解,事實上,當我們在某個物件中定義了一個或者多個訊號,這些訊號與另外若干個物件中的槽相關聯,如果我們要切斷這些關聯的話,就可以利用這個方法,非常之簡潔。
  
  disconnect( myObject, 0, 0, 0 )
  或者
  myObject->disconnect()
  
  斷開與某個特定訊號的任何關聯。
  
  disconnect( myObject, SIGNAL(mySignal()), 0, 0 )
  或者
  myObject->disconnect( SIGNAL(mySignal()) )
  
  斷開兩個物件之間的關聯。
  
  disconnect( myObject, 0, myReceiver, 0 )
  或者
  myObject->disconnect( myReceiver )
  
  在disconnect函式中0可以用作一個萬用字元,分別表示任何訊號、任何接收物件、接收物件中的任何槽函式。但是發射者sender不能為0,其它三個引數的值可以等於0。
  
  五、元物件工具
  元物件編譯器moc(meta object compiler)對C++檔案中的類宣告進行分析併產生用於初始化元物件的C++程式碼,元物件包含全部訊號和槽的名字以及指向這些函式的指標。
  
  moc讀C++原始檔,如果發現有Q_OBJECT宏宣告的類,它就會生成另外一個C++原始檔,這個新生成的檔案中包含有該類的元物件程式碼。例如,假設我們有一個標頭檔案mysignal.h,在這個檔案中包含有訊號或槽的宣告,那麼在編譯之前 moc 工具就會根據該檔案自動生成一個名為mysignal.moc.h的C++原始檔並將其提交給編譯器;類似地,對應於mysignal.cpp檔案moc工具將自動生成一個名為mysignal.moc.cpp檔案提交給編譯器。
  
  元物件程式碼是signal/slot機制所必須的。用moc產生的C++原始檔必須與類實現一起進行編譯和連線,或者用#

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617542/viewspace-949518/,如需轉載,請註明出處,否則將追究法律責任。

相關文章