[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的部件類中已經定義了一些信號和槽,但是更多的做法是子類化這個部件,然後添加自己的信號和槽來實現想要的功能。
在前麵使用過的信號和槽的關聯,都是一個信號對應一個槽。其實,一個信號可以關聯到多個槽上,多個信號也可以關聯到同一個槽上,甚至,一個信號還可以關聯到另一個信號上,如下圖所示。如果存在多個槽與某個信號相關聯,那麼,當這個信號被發射時,這些槽將會一個接一個地執行,但是它們執行的順序是隨機的,無法指定它們的執行順序。
![]() 下麵通過一個簡單的例子來進一步講解信號和槽的相關知識。這個例子實現的效果是:在主界麵中創建一個對話框,在這個對話框中可以輸入數值,當按下確定按鈕時關閉對話框並且將輸入的數值通過信號發射出去,而在主界麵中接收該信號並且顯示數值。
新建Qt Gui應用,項目名稱為“mySignalSlot”,基類選擇QWidget,然後類名保持“Widget”不變。項目建立完成後,向項目中添加新文件,模板選擇Qt分類中的“Qt設計師界麵類”,界麵模板選擇“Dialog without Buttons”,類名為“MyDialog”。完成後首先在mydialog.h文件中添加代碼來聲明一個信號:
signals:
void dlgReturn(int);
// 自定義的信號
聲明一個信號要使用signals關鍵字,在signals前麵不能使用public、private和protected等限定符,因為隻有定義該信號的類及其子類才可以發射該信號。而且信號隻用聲明,不需要也不能對它進行定義實現。還要注意,信號沒有返回值,隻能是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關鍵字。一個槽可以是private、public或者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));
}
現在大家可以運行一下程序查看效果。如下圖所示。
![]() 這個程序中自定義了信號和槽,可以看到它們的使用是很簡單的,隻需要對它們進行關聯,然後在適當的時候發射信號就行。下麵列舉一下使用信號和槽應該注意的幾點:
二、信號和槽的自動關聯
信號和槽還有一種自動關聯方式,例如前麵程序中在設計模式直接生成的按鈕的單擊信號的槽,就是使用的這種方式: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示例程序中也使用了這個類,大家也可以參考一下這個程序。在這裏便不再詳細講述這個類的使用了。
在本節的最後,來看一下信號和槽機製的特色和優越性:
結語
雖然信號和槽機製提供了高度的靈活性,但就其性能而言,還是慢於回調機製的。當然,這點性能差異通常在一個應用程序中是很難體現出來的。
涉及到的代碼: ![]() ![]() |
最後更新:2017-04-03 14:54:15