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