閱讀157 返回首頁    go 阿裏雲 go 技術社區[雲棲]


[Qt教程] 第44篇 進階(四)信號和槽

[Qt教程] 第44篇 進階(四)信號和槽

樓主
 發表於 2013-9-12 16:48:59 | 查看: 298| 回複: 0
信號和槽

版權聲明

該文章原創於Qter開源社區




導語


在前麵的內容中已經多次用到過信號和槽了,這一節我們將詳細講解信號和槽的機製和使用方式。大家可以在幫助中查看Signals& Slots關鍵字。


環境:Windows Xp + Qt 4.8.5+QtCreator2.8.0


目錄



一、信號和槽機製
二、信號和槽的自動關聯
三、信號和槽的高級應用



正文


一、信號和槽機製

信號和槽用於兩個對象之間的通信,信號和槽機製是Qt的核心特征,也是Qt不同於其他開發框架的最突出的特征。在GUI編程中,當改變了一個部件時,總希望其他部件也能了解到該變化。更一般來說,我們希望任何對象都可以和其他對象進行通信。例如,如果用戶點擊了關閉按鈕,我們希望可以執行窗口的close()函數來關閉窗口。為了實現對象間的通信,一些工具包中使用了回調(callback)機製,而在Qt中,使用了信號和槽來進行對象間的通信。當一個特殊的事情發生時便可以發射一個信號,比如按鈕被單擊;而槽就是一個函數,它在信號發射後被調用,來響應這個信號。在Qt的部件類中已經定義了一些信號和槽,但是更多的做法是子類化這個部件,然後添加自己的信號和槽來實現想要的功能。
       在前麵使用過的信號和槽的關聯,都是一個信號對應一個槽。其實,一個信號可以關聯到多個槽上,多個信號也可以關聯到同一個槽上,甚至,一個信號還可以關聯到另一個信號上,如下圖所示。如果存在多個槽與某個信號相關聯,那麼,當這個信號被發射時,這些槽將會一個接一個地執行,但是它們執行的順序是隨機的,無法指定它們的執行順序。

01.jpg 


下麵通過一個簡單的例子來進一步講解信號和槽的相關知識。這個例子實現的效果是:在主界麵中創建一個對話框,在這個對話框中可以輸入數值,當按下確定按鈕時關閉對話框並且將輸入的數值通過信號發射出去,而在主界麵中接收該信號並且顯示數值。
新建Qt Gui應用,項目名稱為“mySignalSlot”,基類選擇QWidget,然後類名保持“Widget”不變。項目建立完成後,向項目中添加新文件,模板選擇Qt分類中的“Qt設計師界麵類”,界麵模板選擇“Dialog without Buttons”,類名為“MyDialog”。完成後首先在mydialog.h文件中添加代碼來聲明一個信號:


signals:
    void dlgReturn(int);                  // 自定義的信號


       聲明一個信號要使用signals關鍵字,在signals前麵不能使用publicprivateprotected等限定符,因為隻有定義該信號的類及其子類才可以發射該信號。而且信號隻用聲明,不需要也不能對它進行定義實現。還要注意,信號沒有返回值,隻能是void類型的。因為隻有QObject類及其子類派生的類才能使用信號和槽機製,這裏的MyDialog類繼承自QDialog類,QDialog類又繼承自QWidget類,QWidget類是QObject類的子類,所以這裏可以使用信號和槽。不過,使用信號和槽,還必須在類聲明的最開始處添加Q_OBJECT宏,在這個程序中,類的聲明是自動生成的,已經添加了這個宏。


在mydialog.ui對應的界麵中添加一個Spin Box部件和一個Push Button部件,將pushButton的顯示文本改為“確定”。然後轉到pushButton的單擊信號clicked()槽,更改如下:


void MyDialog::on_pushButton_clicked()   // 確定按鈕
{
   int value = ui->spinBox->value();    // 獲取輸入的數值
   emit dlgReturn(value);               // 發射信號
   close();                             // 關閉對話框
}


       當單擊確定按鈕時,便獲取spinBox部件中的數值,然後使用自定義的信號將其作為參數發射出去。發射一個信號要使用emit關鍵字,例如程序中發射了dlgReturn()信號。
       然後到widget.h文件中添加自定義槽的聲明:


private slots:
    void showValue(int value);


       聲明一個槽需要使用slots關鍵字。一個槽可以是privatepublic或者protected類型的,槽也可以被聲明為虛函數,這與普通的成員函數是一樣的,也可以像調用一個普通函數一樣來調用槽。槽的最大特點就是可以和信號關聯。
下麵打開widget.ui文件,向界麵上拖入一個Label部件,然後更改其文本為“獲取的值是:”。然後進入widget.cpp文件中添加頭文件#include "mydialog.h",再在構造函數中添加代碼:


MyDialog *dlg = new MyDialog(this);
// 將對話框中的自定義信號與主界麵中的自定義槽進行關聯
connect(dlg,SIGNAL(dlgReturn(int)),this,SLOT(showValue(int)));
dlg->show();


       這裏創建了一個MyDialog,並且使用Widget作為父部件。然後將MyDialog類的dlgReturn()信號與Widget類的showValue()槽進行關聯。信號和槽進行關聯,使用的是QObject類的connect()函數,這個函數的原型如下:


bool QObject::connect ( const QObject *sender, const char * signal, const QObject * receiver, const char * method,Qt::ConnectionType type = Qt::AutoConnection )


它的第一個參數為發送信號的對象,例如這裏的dlg;第二個參數是要發送的信號,這裏是SIGNAL(dlgReturn(int));第三個參數是接收信號的對象,這裏是this,表明是本部件,即Widget,當這個參數為this時,也可以將這個參數省略掉,因為connect()函數還有另外一個重載形式,該參數默認為this;第四個參數是要執行的槽,這裏是SLOT(showValue(int))。對於信號和槽,必須使用SIGNAL()和SLOT()宏,它們可以將其參數轉化為const char* 類型。connect()函數的返回值為bool類型,當關聯成功時返回true。還要注意,在調用這個函數時信號和槽的參數隻能有類型,不能有變量,例如寫成SLOT(showValue(int value))是不對的。對於信號和槽的參數問題,基本原則是信號中的參數類型要和槽中的參數類型相對應,而且信號中的參數可以多於槽中的參數,但是不能反過來,如果信號中有多餘的參數,那麼它們將被忽略。下麵介紹一下connect()函數的最後一個參數,它表明了關聯的方式,其默認值是Qt::AutoConnection,這裏還有其他幾個選擇,在編程中一般使用默認值,例如這裏,在MyDialog類中使用emit發射了信號之後,就會執行槽,隻有等槽執行完了以後,才會執行emit語句後麵的代碼。大家也可以將這個參數改為Qt::QueuedConnection,這樣在執行完emit語句後便會立即執行其後麵的代碼,而不管槽是否已經執行。當不再使用這個關聯時,還可以使用disconnect()函數來斷開關聯。



下麵是自定義槽的實現,在這裏隻是簡單的將參數傳遞來的數值顯示在了標簽上。因為這裏使用了中文,所以大家記著在main.cpp文件中添加相關代碼。


void Widget::showValue(int value)         // 自定義槽
{
   ui->label->setText(tr("獲取的值是:%1").arg(value));
}


       現在大家可以運行一下程序查看效果。如下圖所示。


02.jpg



這個程序中自定義了信號和槽,可以看到它們的使用是很簡單的,隻需要對它們進行關聯,然後在適當的時候發射信號就行。下麵列舉一下使用信號和槽應該注意的幾點:


  • 需要繼承自QObject或其子類;
  • 在類聲明的最開始處添加Q_OBJECT宏;
  • 槽中的參數的類型要和信號的參數的類型相對應,且不能比信號的參數多;
  • 信號隻用聲明,沒有定義,且返回值為void類型。



二、信號和槽的自動關聯



信號和槽還有一種自動關聯方式,例如前麵程序中在設計模式直接生成的按鈕的單擊信號的槽,就是使用的這種方式:on_pushButton_clicked(),它由“on”、部件的objectName和信號三部分組成,中間用下劃線隔開。這樣組織的名稱的槽就可以直接和信號關聯,而不用再使用connect()函數。不過使用這種方式還要進行其他設置,而前麵之所以可以直接使用,是因為程序中默認已經進行了設置。下麵來看一個簡單的例子。


       新建Qt Gui應用,項目名稱為“mySignalSlot2”,基類選擇QWidget,然後類名保持“Widget”不變。完成後先在widget.h文件中進行函數聲明:


private slots:
    void on_myButton_clicked();


這裏自定義了一個槽,它使用自動關聯。然後在widget.cpp文件中添加頭文件#include <QPushButton>,再將構造函數的內容更改如下:


Widget::Widget(QWidget *parent) :
   QWidget(parent),
   ui(new Ui::Widget)
{
   QPushButton *button = new QPushButton(this); // 創建按鈕
   button->setObjectName("myButton");           // 指定按鈕的對象名
   ui->setupUi(this);                      //要在定義了部件以後再調用這個函數
}


       因為在setupUi()函數中調用了connectSlotsByName()函數,所以要使用自動關聯的部件的定義都要放在setupUi()函數之前,而且還必須使用setObjectName()函數指定它們的objectName,隻有這樣才能正常使用自動關聯。下麵是槽的定義:


void Widget::on_myButton_clicked()          // 使用自動關聯
{
   close();
}


       這裏進行了關閉部件的操作。對於槽的函數名,中間要使用前麵指定的objectName,這裏是“myButton”。現在運行一下程序,單擊按鈕,發現可以正常關閉窗口。


       可以看到,如果要使用信號和槽的自動關聯,就必須在connectSlotsByName()函數之前進行部件的定義,而且還要指定部件的objectName,並且自動關聯中必須使用Qt中已經定義的信號,而不能是自定義的信號。鑒於這些約束,雖然自動關聯形式上很簡單,但是實際編寫代碼時卻很少使用。而且,在定義一個部件時,很希望明確的使用connect()函數來對其進行信號和槽的關聯,這樣當別人看到這個部件定義時,就可以知道和它相關的信號和槽的關聯了。而使用自動關聯,卻沒有這麼明了。



三、信號和槽的高級應用



       有時我們希望獲得信號發送者的信息,在Qt中提供了QObject::sender()函數來返回發送該信號的對象的指針。但是如果有多個信號關聯到了同一個槽上,而在該槽中需要對每一個信號進行不同的處理,使用上麵的方法就很麻煩了。對於這種情況,便可以使用QSignalMapper類。QSignalMapper可以被叫做信號映射器,它可以實現對多個相同部件的相同信號進行映射,為其添加字符串或者數值參數,然後再發射出去。對於這個類的使用,大家可以參考《Qt及Qt Quick開發實戰精解》1.3.3小節,那裏有這個類的實際應用。還有就是Qt的演示程序中的Tools分類下的Input Panel示例程序中也使用了這個類,大家也可以參考一下這個程序。在這裏便不再詳細講述這個類的使用了。


       在本節的最後,來看一下信號和槽機製的特色和優越性:


  • 信號和槽機製是類型安全的,相關聯的信號和槽的參數必須匹配;
  • 信號和槽是鬆耦合的,信號發送者不知道也不需要知道接受者的信息;
  • 信號和槽可以使用任意類型的任意數量的參數。




結語


雖然信號和槽機製提供了高度的靈活性,但就其性能而言,還是慢於回調機製的。當然,這點性能差異通常在一個應用程序中是很難體現出來的。




涉及到的代碼:  mySignalSlot.rar (2.84 KB, 下載次數: 1)  mySignalSlot2.rar (1.55 KB, 下載次數: 0) 

最後更新:2017-04-03 14:54:15

  上一篇:go [Qt教程] 第35篇 網絡(五)獲取本機網絡信息
  下一篇:go JavaBean的應用