<a target="_blank" href="http://bbs.qter.org/forum.php?mod=viewthread&tid=697">樓主</a>

發表于 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));
現在大家可以運作一下程式檢視效果。如下圖所示。
這個程式中自定義了信号和槽,可以看到它們的使用是很簡單的,隻需要對它們進行關聯,然後在适當的時候發射信号就行。下面列舉一下使用信号和槽應該注意的幾點:
需要繼承自qobject或其子類;
在類聲明的最開始處添加q_object宏;
槽中的參數的類型要和信号的參數的類型相對應,且不能比信号的參數多;
信号隻用聲明,沒有定義,且傳回值為void類型。
信号和槽還有一種自動關聯方式,例如前面程式中在設計模式直接生成的按鈕的單擊信号的槽,就是使用的這種方式:on_pushbutton_clicked(),它由“on”、部件的objectname和信号三部分組成,中間用下劃線隔開。這樣組織的名稱的槽就可以直接和信号關聯,而不用再使用connect()函數。不過使用這種方式還要進行其他設定,而前面之是以可以直接使用,是因為程式中預設已經進行了設定。下面來看一個簡單的例子。
建立qt gui應用,項目名稱為“mysignalslot2”,基類選擇qwidget,然後類名保持“widget”不變。完成後先在widget.h檔案中進行函數聲明:
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()函數來對其進行信号和槽的關聯,這樣當别人看到這個部件定義時,就可以知道和它相關的信号和槽的關聯了。而使用自動關聯,卻沒有這麼明了。
panel示例程式中也使用了這個類,大家也可以參考一下這個程式。在這裡便不再詳細講述這個類的使用了。
在本節的最後,來看一下信号和槽機制的特色和優越性:
信号和槽機制是類型安全的,相關聯的信号和槽的參數必須比對;
信号和槽是松耦合的,信号發送者不知道也不需要知道接受者的資訊;
信号和槽可以使用任意類型的任意數量的參數。
結語
雖然信号和槽機制提供了高度的靈活性,但就其性能而言,還是慢于回調機制的。當然,這點性能差異通常在一個應用程式中是很難展現出來的。
涉及到的代碼:
kb, 下載下傳次數: 1)
kb, 下載下傳次數: 0)