Qt 信号和槽
信號(hào)和槽
信號(hào)和槽用于對(duì)象間的通訊。信號(hào)/槽機(jī)制是Qt的一個(gè)中心特征并且也許是Qt與 其它工具包的最不相同的部分。
在圖形用戶界面編程中,我們經(jīng)常希望一個(gè)窗口部件的一個(gè)變化被通知給另一個(gè) 窗口部件。更一般地,我們希望任何一類的對(duì)象可以和其它對(duì)象進(jìn)行通訊。例如,如 果我們正在解析一個(gè)XML文件,當(dāng)我們遇到一個(gè)新的標(biāo)簽時(shí),我們也許希望通知列表 視圖我們正在用來(lái)表達(dá)XML文件的結(jié)構(gòu)。
較老的工具包使用一種被稱作回調(diào)的通訊方式來(lái)實(shí)現(xiàn)同一目的。回調(diào)是指一個(gè)函 數(shù)的指針,所以如果你希望一個(gè)處理函數(shù)通知你一些事件,你可以把另一個(gè)函數(shù)(回 調(diào))的指針傳遞給處理函數(shù)。處理函數(shù)在適當(dāng)?shù)臅r(shí)候調(diào)用回調(diào)。回調(diào)有兩個(gè)主要缺 點(diǎn)。首先他們不是類型安全的。我們從來(lái)都不能確定處理函數(shù)使用了正確的參數(shù)來(lái)調(diào) 用回調(diào)。其次回調(diào)和處理函數(shù)是非常強(qiáng)有力地聯(lián)系在一起的,因?yàn)樘幚砗瘮?shù)必須知道 要調(diào)用哪個(gè)回調(diào)。
一個(gè)關(guān)于一些信號(hào)和槽連接的摘要圖
在Qt中我們有一種可以替代回調(diào)的技術(shù)。我們使用信號(hào)和槽。當(dāng) 一個(gè)特定事件發(fā)生的時(shí)候,一個(gè)信號(hào)被發(fā)射。Qt的窗口部件有很多預(yù)定義的信號(hào), 但是我們總是可以通過(guò)繼承來(lái)加入我們自己的信號(hào)。槽就是一個(gè)可以被調(diào)用處理特定 信號(hào)的函數(shù)。Qt的窗口部件又很多預(yù)定義的槽,但是通常的習(xí)慣是你可以加入自己的 槽,這樣你就可以處理你所感興趣的信號(hào)。
信號(hào)和槽的機(jī)制是類型安全的:一個(gè)信號(hào)的簽名必須與它的接收槽的簽名相匹 配。(實(shí)際上一個(gè)槽的簽名可以比它接收的信號(hào)的簽名少,因?yàn)樗梢院雎灶~外的 簽名。)因?yàn)楹灻且恢碌?#xff0c;編譯器就可以幫助我們檢測(cè)類型不匹配。信號(hào)和槽是 寬松地聯(lián)系在一起的:一個(gè)發(fā)射信號(hào)的類不用知道也不用注意哪個(gè)槽要接收這個(gè)信 號(hào)。Qt的信號(hào)和槽的機(jī)制可以保證如果你把一個(gè)信號(hào)和一個(gè)槽連接起來(lái),槽會(huì)在正 確的時(shí)間使用信號(hào)的參數(shù)而被調(diào)用。信號(hào)和槽可以使用任何數(shù)量、任何類型的參 數(shù)。它們是完全類型安全的:不會(huì)再有回調(diào)核心轉(zhuǎn)儲(chǔ)(core dump)。
從QObject類或者它的一個(gè)子類 (比如QWidget類)繼承的所有類可以包含信號(hào)和槽。 當(dāng)對(duì)象改變它們的狀態(tài)的時(shí)候,信號(hào)被發(fā)送,從某種意義上講,它們也許對(duì)外面的 世界感興趣。這就是所有的對(duì)象通訊時(shí)所做的一切。它不知道也不注意無(wú)論有沒(méi)有 東西接收它所發(fā)射的信號(hào)。這就是真正的信息封裝,并且確保對(duì)象可以用作一個(gè)軟 件組件。
一個(gè)信號(hào)和槽連接的例子
槽可以用來(lái)接收信號(hào),但它們是正常的成員函數(shù)。一個(gè)槽不知道 它是否被任意信號(hào)連接。此外,對(duì)象不知道關(guān)于這種通訊機(jī)制和能夠被用作一個(gè)真正 的軟件組件。
你可以把許多信號(hào)和你所希望的單一槽相連,并且一個(gè)信號(hào)也可以和你所期望的 許多槽相連。把一個(gè)信號(hào)和另一個(gè)信號(hào)直接相連也是可以的。(這時(shí),只要第一個(gè)信 號(hào)被發(fā)射時(shí),第二個(gè)信號(hào)立刻就被發(fā)射。)
總體來(lái)看,信號(hào)和槽構(gòu)成了一個(gè)強(qiáng)有力的組件編程機(jī)制。
一個(gè)小例子
一個(gè)最小的C++類聲明如下:
class Foo{public:Foo();int value() const { return val; }void setValue( int );private:int val;};一個(gè)小的Qt類如下:
class Foo : public QObject{Q_OBJECTpublic:Foo();int value() const { return val; }public slots:void setValue( int );signals:void valueChanged( int );private:int val;};這個(gè)類有同樣的內(nèi)部狀態(tài),和公有方法來(lái)訪問(wèn)狀態(tài),但是另外它也支持使用信號(hào) 和槽的組件編程:這個(gè)類可以通過(guò)發(fā)射一個(gè)信號(hào),valueChanged(),來(lái)告 訴外面的世界它的狀態(tài)發(fā)生了變化,并且它有一個(gè)槽,其它對(duì)象可以發(fā)送信號(hào)給這個(gè) 槽。
所有包含信號(hào)和/或者槽的類必須在它們的聲明中提到Q_OBJECT。
槽可以由應(yīng)用程序的編寫者來(lái)實(shí)現(xiàn)。這里是Foo::setValue()的一個(gè)可能的實(shí)現(xiàn):
void Foo::setValue( int v ){if ( v != val ) {val = v;emit valueChanged(v);}}emit valueChanged(v)這一行從對(duì)象中發(fā)射valueChanged信 號(hào)。正如你所能看到的,你通過(guò)使用emit signal(arguments)來(lái)發(fā)射信號(hào)。
下面是把兩個(gè)對(duì)象連接在一起的一種方法:
Foo a, b;connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));b.setValue( 11 ); // a == undefined b == 11a.setValue( 79 ); // a == 79 b == 79b.value();調(diào)用a.setValue(79)會(huì)使a發(fā)射一個(gè)valueChanged()?信號(hào),b將會(huì)在它的setValue()槽中接收這個(gè)信號(hào),也就是b.setValue(79)?被調(diào)用。接下來(lái)b會(huì)發(fā)射同樣的valueChanged()信號(hào),但是因?yàn)?沒(méi)有槽被連接到b的valueChanged()信號(hào),所以沒(méi)有發(fā)生任何事 (信號(hào)消失了)。
注意只有當(dāng)v != val的時(shí)候setValue()函數(shù)才會(huì)設(shè)置這個(gè)值 并且發(fā)射信號(hào)。這樣就避免了在循環(huán)連接的情況下(比如b.valueChanged()?和a.setValue()連接在一起)出現(xiàn)無(wú)休止的循環(huán)的情況。
這個(gè)例子說(shuō)明了對(duì)象之間可以在互相不知道的情況下一起工作,只要在最初的時(shí)在 它們中間建立連接。
預(yù)處理程序改變或者移除了signals、slots和emit?這些關(guān)鍵字,這樣就可以使用標(biāo)準(zhǔn)的C++編譯器。
在一個(gè)定義有信號(hào)和槽的類上運(yùn)行moc。這樣就會(huì)生成 一個(gè)可以和其它對(duì)象文件編譯和連接成引用程序的C++源文件。
信號(hào)
當(dāng)對(duì)象的內(nèi)部狀態(tài)發(fā)生改變,信號(hào)就被發(fā)射,在某些方面對(duì)于 對(duì)象代理或者所有者也許是很有趣的。只有定義了一個(gè)信號(hào)的類和它的子類才能發(fā)射 這個(gè)信號(hào)。
例如,一個(gè)列表框同時(shí)發(fā)射highlighted()和activated()這 兩個(gè)信號(hào)。絕大多數(shù)對(duì)象也許只對(duì)activated()這個(gè)信號(hào)感興趣,但是有時(shí) 想知道列表框中的哪個(gè)條目在當(dāng)前是高亮的。如果兩個(gè)不同的類對(duì)同一個(gè)信號(hào)感興趣, 你可以把這個(gè)信號(hào)和這兩個(gè)對(duì)象連接起來(lái)。
當(dāng)一個(gè)信號(hào)被發(fā)射,它所連接的槽會(huì)被立即執(zhí)行,就像一個(gè)普通函數(shù)調(diào)用一樣。 信號(hào)/槽機(jī)制完全不依賴于任何一種圖形用戶界面的事件回路。當(dāng)所有的槽都返回后?emit也將返回。
如果幾個(gè)槽被連接到一個(gè)信號(hào),當(dāng)信號(hào)被發(fā)射時(shí),這些槽就會(huì)被按任意順序一個(gè) 接一個(gè)地執(zhí)行。
信號(hào)會(huì)由moc自動(dòng)生成并且一定不要在.cpp文件中 實(shí)現(xiàn)。它們也不能有任何返回類型(比如使用void)。
關(guān)于參數(shù)需要注意。我們的經(jīng)驗(yàn)顯示如果信號(hào)和槽不使用特殊的類型, 它們都可以多次使用。如果QScrollBar::valueChanged() 使用了一個(gè)特殊的類型,比如hypothetical QRangeControl::Range,它就只能被連 接到被設(shè)計(jì)成可以處理QRangeControl的槽。簡(jiǎn) 單的和教程1的第5部分一樣的程序?qū)⑹遣豢?能的。
槽
當(dāng)一個(gè)和槽連接的信號(hào)被發(fā)射的時(shí)候,這個(gè)操被調(diào)用。槽也是 普通的C++函數(shù)并且可以像它們一樣被調(diào)用;它們唯一的特點(diǎn)就是它們可以被信號(hào)連 接。槽的參數(shù)不能含有默認(rèn)值,并且和信號(hào)一樣,為了槽的參數(shù)而使用自己特定的類 型是很不明智的。
因?yàn)椴劬褪瞧胀ǔ蓡T函數(shù),但卻有一點(diǎn)非常有意思的東西,它們也和普通成員函 數(shù)一樣有訪問(wèn)權(quán)限。一個(gè)槽的訪問(wèn)權(quán)限決定了誰(shuí)可以和它相連:
一個(gè)public slots:區(qū)包含了任何信號(hào)都可以相連的槽。這對(duì)于組件編 程來(lái)說(shuō)非常有用:你生成了許多對(duì)象,它們互相并不知道,把它們的信號(hào)和槽連接起 來(lái),這樣信息就可以正確地傳遞,并且就像一個(gè)鐵路模型,把它打開(kāi)然后讓它跑起來(lái)。
一個(gè)protected slots:區(qū)包含了之后這個(gè)類和它的子類的信號(hào)才能連接 的槽。這就是說(shuō)這些槽只是類的實(shí)現(xiàn)的一部分,而不是它和外界的接口。
一個(gè)private slots:區(qū)包含了之后這個(gè)類本身的信號(hào)可以連接的槽。這 就是說(shuō)它和這個(gè)類是非常緊密的,甚至它的子類都沒(méi)有獲得連接權(quán)利這樣的信任。
你也可以把槽定義為虛的,這在實(shí)踐中被發(fā)現(xiàn)也是非常有用的。
信號(hào)和槽的機(jī)制是非常有效的,但是它不像“真正的”回調(diào)那樣快。信號(hào)和槽稍 微有些慢,這是因?yàn)樗鼈兯峁┑撵`活性,盡管在實(shí)際應(yīng)用中這些不同可以被忽略。 通常,發(fā)射一個(gè)和槽相連的信號(hào),大約只比直接調(diào)用那些非虛函數(shù)調(diào)用的接收器慢 十倍。這是定位連接對(duì)象所需的開(kāi)銷,可以安全地重復(fù)所有地連接(例如在發(fā)射期 間檢查并發(fā)接收器是否被破壞)并且可以按一般的方式安排任何參數(shù)。當(dāng)十個(gè)非虛 函數(shù)調(diào)用聽(tīng)起來(lái)很多時(shí),舉個(gè)例子來(lái)說(shuō),時(shí)間開(kāi)銷只不過(guò)比任何一個(gè)“new”或者 “delete”操作要少些。當(dāng)你執(zhí)行一個(gè)字符串、矢量或者列表操作時(shí),需要“new”或者 “delete”,信號(hào)和槽僅僅對(duì)一個(gè)完整函數(shù)調(diào)用地時(shí)間開(kāi)銷中的一個(gè)非常小的部分負(fù) 責(zé)。無(wú)論何時(shí)你在一個(gè)槽中使用一個(gè)系統(tǒng)調(diào)用和間接地調(diào)用超過(guò)十個(gè)函數(shù)的時(shí)間是 相同的。在一臺(tái)i585-500機(jī)器上,你每秒鐘可以發(fā)射2,000,000個(gè)左右連接到一個(gè) 接收器上的信號(hào),或者發(fā)射1,200,000個(gè)左右連接到兩個(gè)接收器的信號(hào)。信號(hào)和槽 機(jī)制的簡(jiǎn)單性和靈活性對(duì)于時(shí)間的開(kāi)銷來(lái)說(shuō)是非常值得的,你的用戶甚至察覺(jué)不出來(lái)。
元對(duì)象信息
元對(duì)象編譯器(moc) 解析一個(gè)C++文件中的類聲明并且生成初始化元對(duì)象的C++代碼。元對(duì)象包括所有信號(hào) 和槽函數(shù)的名稱,還有這些函數(shù)的指針。(要獲得更多的信息,請(qǐng)看為什么Qt不用模板來(lái)實(shí)現(xiàn)信號(hào)和槽?)
元對(duì)象包括一些額外的信息,比如對(duì)象的類名稱。 你也可以檢查一個(gè)對(duì)象是否繼承了一個(gè)特定的類, 比如:
if ( widget->inherits("QButton") ) {// 是的,它是一個(gè)Push Button、Radio Button或者其它按鈕。}一個(gè)真實(shí)的例子
這是一個(gè)注釋過(guò)的簡(jiǎn)單的例子(代碼片斷選自qlcdnumber.h)。
#include "qframe.h"#include "qbitarray.h"class QLCDNumber : public QFrameQLCDNumber通過(guò)QFrame和QWidget,還有#include這樣的相關(guān)聲明繼承了含有絕大 多數(shù)信號(hào)/槽知識(shí)的QObject。
{Q_OBJECTQ_OBJECT是由預(yù)處理器展開(kāi)聲明幾個(gè)由moc來(lái)實(shí)現(xiàn)的成員函數(shù),如果你得到了幾行 “virtual function QButton::className not defined”這樣的編譯器錯(cuò)誤信息,你也 許忘記運(yùn)行moc或者忘記在連接命令中包含moc輸出。
public:QLCDNumber( QWidget *parent=0, const char *name=0 );QLCDNumber( uint numDigits, QWidget *parent=0, const char *name=0 );它并不和moc直接相關(guān),但是如果你繼承了QWidget,你當(dāng)然想在你的構(gòu)造器中獲 得parent和name這兩個(gè)參數(shù),而且把它們傳遞到父類的構(gòu)造器中。
一些解析器和成員函數(shù)在這里省略掉了,moc忽略了這些成員函數(shù)。
signals:void overflow();當(dāng)QLCDNumber被請(qǐng)求顯示一個(gè)不可能值時(shí),它 發(fā)射一個(gè)信號(hào)。
如果你沒(méi)有留意溢出,或者你認(rèn)為溢出不會(huì)發(fā)生,你可以忽略overflow()信號(hào), 也就是說(shuō)你可以不把它連接到任何一個(gè)槽上。
另一方面如果當(dāng)數(shù)字溢出時(shí),你想調(diào)用兩個(gè)不同的錯(cuò)誤函數(shù),很簡(jiǎn)單地你可 以把這個(gè)信號(hào)和兩個(gè)不同的槽連接起來(lái)。Qt將會(huì)兩個(gè)都調(diào)用(按任意順序)。
public slots:void display( int num );void display( double num );void display( const char *str );void setHexMode();void setDecMode();void setOctMode();void setBinMode();void smallDecimalPoint( bool );一個(gè)槽就是一個(gè)接收函數(shù),用來(lái)獲得其它窗口部件狀態(tài)變或的信息。QLCDNumber 使用它,就像上面的代碼一樣,來(lái)設(shè)置顯示的數(shù)字。因?yàn)閐isplay()是這個(gè) 類和程序的其它的部分的一個(gè)接口,所以這個(gè)槽是公有的。
幾個(gè)例程把QScrollBar的newValue信號(hào)連接到display槽,所以LCD數(shù)字可以繼續(xù)顯示滾動(dòng)條的值。
請(qǐng)注意display()被重載了,當(dāng)你把一個(gè)信號(hào)和這個(gè)槽相連的時(shí)候,Qt將會(huì)選擇適 當(dāng)?shù)陌姹尽H绻褂没卣{(diào),你會(huì)發(fā)現(xiàn)五個(gè)不同的名字并且自己來(lái)跟蹤類型。
一些不相關(guān)的成員函數(shù)已經(jīng)從例子中省略了。
總結(jié)
- 上一篇: android https请求证书过滤白
- 下一篇: 无法完成windows正版认证