## 13.2 信號與槽
信號和槽機制是 Qt 的核心機制之一,要掌握 Qt 編程就需要對信號和槽有所了解。信號和槽是一種高級接口,它們被應用于對象之間的通信,它們是 Qt 的核心特性,也是 Qt不同于其它同類工具包的重要地方之一。
在我們所了解的其它 GUI 工具包中,窗口小部件(widget)都有一個回調函數用于響應 它們觸發的動作,這個回調函數通常是一個指向某個函數的指針。在 Qt 中用信號和槽取代 了上述機制。
1\.信號(signal)
當對象的狀態發生改變時,信號被某一個對象發射( emit)。只有定義過這個信號的類或者其派生類能夠發射這個信號。當一個信號被發射時,與其相關聯的槽將被執行,就象一個正常的函數調用一樣。信號-槽機制獨立于任何 GUI 事件循環。只有當所有的槽正確返 回以后,發射函數(emit)才返回。
如果存在多個槽與某個信號相關聯,那么,當這個信號被發射時,這些槽將會一個接 一個地被執行,但是它們執行的順序將會是不確定的,并且我們不能指定它們執行的順序。
信號的聲明是在頭文件中進行的,并且 moc 工具會注意不要將信號定義在實現文件 中。Qt 用 signals 關鍵字標識信號聲明區,隨后即可聲明自己的信號。 例如,下面定義了 幾個信號:
```
signals:
void yourSignal();
void yourSignal(int x);
```
在上面的語句中,signals 是 Qt 的關鍵字。接下來的一行 void yourSignal(); 定義了信號 yourSignal,這個信號沒有攜帶參數;接下來的一行 void yourSignal(int x);定義 了信號 yourSignal(int x),但是它攜帶一個整形參數,這種情形 類似于重載。
注意,信號和槽函數的聲明一般位于頭文件中,同時在類聲明的開始位置必須加上 Q_OBJECT 語句,這條語句是不可缺少的,它將告訴編譯器在編譯之前必須先應用 moc 工具 進行擴展。關鍵字 signals 指出隨后開始信號的聲明,這里 signals 用的是復數形式而非 單數,siganls 沒有 public、private、protected 等屬性,這點不同于 slots。另外, signals、slots 關鍵字是 QT 自己定義的,不是 C++中的關鍵字。
還有,信號的聲明類似于函數的聲明而非變量的聲明,左邊要有類型,右邊要有括 號,如果要向槽中傳遞參數的話,在括號中指定每個形式參數的類型,當然,形式參數的個 數可以多于一個。
從形式上講,信號的聲明與普通的 C++函數是一樣的,但是信號沒有定義函數實現。另 外,信號的返回 類型都是 void,而 C++函數的返回值可以有豐富的類型。
注意,signal 代碼會由 moc 自動生成,moc 將其轉化為標準的 C++語句,C++預處理 器會認為自己處理的是標準 C++源文件。所以大家不要在自己的 C++實現文件實現 signal。
2\.槽(slot)
槽是普通的 C++成員函數,可以被正常調用,不同之處是它們可以與信號( signal)相 關聯。當與其關聯的信號被發射時,這個槽就會被調用。槽可以有參數,但槽的參數不能有 缺省值。
槽也和普通成員函數一樣有訪問權限。槽的訪問權限決定了誰可以和它相連。 通常, 槽也分為三種類型,即 public slots、private slots 和 protected slots。
public slots:在這個代碼區段內聲明的槽意味著任何對象都可將信號與之相連接。 這對于組件編程來說非常有用:你生成了許多對象,它們互相并不知道,把它們的信號和槽 連接起來,這樣信息就可以正確地傳遞,并且就像一個小孩子喜歡玩耍的鐵路軌道上的火車 模型,把它打開然后讓它跑起來。
protected slots:在這個代碼區段內聲明的槽意味著當前類及其子類可以將信號與之 相關聯。這些槽只是類的實現的一部分,而不是它和外界的接口。
private slots:在這個代碼區段內聲明的槽意味著只有類自己可以將信號與之相關 聯。這就是說這些槽和這個類是非常緊密的,甚至它的子類都沒有獲得連接權利這樣的信 任。
通常,我們使用 public 和 private 聲明槽是比較常見的,建議盡量不要使用 protected 關鍵字來修飾槽的屬性。此外,槽也能夠聲明為虛函數。
槽的聲明也是在頭文件中進行的。例如,下面聲明了幾個槽:
```
public slots:
void yourSlot();
void yourSlot(int x);
```
注意,關鍵字 slots 指出隨后開始槽的聲明,這里 slots 用的也是復數形式。
3\.信號與槽的關聯
槽和普通的 C++成員函數幾乎是一樣的-可以是虛函數;可以被重載;可以是共有的、 保護的或是私有的,并且也可以被其它 C++成員函數直接調用;還有,它們的參數可以是任 意類型。唯一不同的是:槽還可以和信號連接在一起,在這種情況下,每當發射這個信號的 時候,就會自動調用這個槽。
connect()語句看起來會是如下的樣子:
```
connect(sender,SIGNAL(signal),receiver,SLOT(slot));
```
這里的 sender 和 receiver 是指向 QObject 的指針,signal 和 slot 是不帶參數的函數 名。實際上,SIGNAL()宏和 SLOT()會把它們的參數轉換成相應的字符串。
到目前為止,在已經看到的實例中,我們已經把不同的信號和不同的槽連接在了一 起。但這里還需要考慮一些其他的可能性。
(1) 一個信號可以連接多個槽
```
connect(slider,SIGNAL(valueChanged(int)),spinBox,SLOT(setValue(int)));
connect(slider,SIGNAL(valueChanged(int)),this,SLOT(updateStatusBarIndicator(int)));
```
在發射這個信號的時候,會以不確定的順序一個接一個的調用這些槽。
(2) 多個信號可以連接同一個槽
```
connect()
```
無論發射的是哪一個信號,都會調用這個槽。
(3) 一個信號可以與另外一個信號相連接
```
connect(lineEdit,SIGNAL(textChanged(const Qstring &)),this,SIGNAL(updateRecord(const Qstring &)));
```
當發射第一個信號時,也會發射第二個信號。除此之外,信號與信號之間的連接和信 號與槽之間的連接是難以區分的。
(4) 連接可以被移除
```
disconnect(lcd,SIGNAL(overflow()),this,SLOT(handleMathError()));
```
這種情況較少用到,因為當刪除對象時, Qt 會自動移除和這個對象相關的所有連接。
(5) 要把信號成功連接到槽(或者連接到另外一個信號),它們的參數必須具有相同的順序 和相同的類型
```
connect(ftp,SIGNAL(rawCommandReply(int,const QString&)),this,SLOT(processReply(int,const QString &)));
```
(6) 如果信號的參數比它所連接的槽的參數多,那么多余的參數將會被簡單的忽略掉
```
connect(ftp,SIGNAL(rawCommandReply(int,const Qstring &)),this,SLOT(checkErrorCode(int)));
```
還有,如果參數類型不匹配,或者如果信號或槽不存在,則當應用程序使用調試模式 構建后,Qt 會在運行時發出警告。與之相類似的是,如果在信號和槽的名字中包含了參數 名,Qt 也會發出警告。
信號和槽機制本身是在 QObject 中實現的,并不只局限于圖形用戶界面編程中。這種 機制可以用于任何 QObject 的子類中。
當指定信號 signal 時必須使用 Qt 的宏 SIGNAL(),當指定槽函數時必須使用宏 SLOT()。如果發射者與接收者屬于同一個對象的話,那么在 connect 調用中接收者參數可 以省略。
例如,下面定義了兩個對象:標簽對象 label 和滾動條對象 scroll,并將 valueChanged()信號與標簽對象的 setNum()相關聯,另外信號還攜帶了一個整形參數,這樣標簽總是顯示滾動條所處位置的值。
```
QLabel *label = new QLabel;
QScrollBar *scroll = new QScrollBar;
QObject::connect( scroll, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)) );
```
4\.信號和槽連接示例
以下是 QObject 子類的示例:
```
class BankAccount : public QObject
{
Q_OBJECT
public:
BankAccount() { curBalance = 0; }
int balance() const { return curBalance; }
public slots:
void setBalance(int newBalance);
signals:
void balanceChanged(int newBalance);
private:
int currentBalance;
};
```
與多數 C++ 類的風格類似,BankAccount 類擁有構造函數、balance() “讀取”函數 和 setBalance() “設置”函數。它還擁有 balanceChanged() 信號,帳戶余額更改時將 發出此信號。發出信號時,與它相連 的槽將被執行。
Set 函數是在公共槽區中聲明的,因此它是一個槽。槽既可以作為成員函數,與其他 任何函數一樣調用,也可以與信號相連。以下是 setBalance() 槽的實現過程:
```
void BankAccount::setBalance(int newBalance)
{
if (newBalance != currentBalance)
{
currentBalance = newBalance;
emit balanceChanged(currentBalance);
}
}
```
語句 emit balanceChanged(currentBalance);將發出 balanceChanged() 信號,并使 用當前新余額作為其參數。
關鍵字 emit 類似于“signals”和“slots”,由 Qt 提供,并由 C++ 預處理器轉換成標準 C++ 語句。
以下示例說明如何連接兩個 BankAccount 對象:
```
BankAccount x, y;
connect(&x, SIGNAL(balanceChanged(int)), &y, SLOT(setBalance(int)));
x.setBalance(2450);
```
當 x 中的余額設置為 2450 時,系統將發出 balanceChanged() 信號。y 中的 setBalance() 槽收到此信號后,將 y 中的余額設置為 2450。一個對象的信號可以與多個 不同槽相連,多個信號也可以與特定對象中的某一個槽相連。參數類型相同的信號和槽可以 互相連接。槽的參數個數可以少于信號的參數個數,這時多余的參數將被忽略。
5\.需要注意的問題
信號與槽機制是比較靈活的,但有些局限性我們必須了解,這樣在實際的使用過程中才能夠做到有的放矢,避免產生一些錯誤。下面就介紹一下這方面的情況。
(1) 信號與槽的效率是非常高的,但是同真正的回調函數比較起來,由于增加了靈活 性,因此在速度上還是有所損失,當然這種損失相對來說是比較小的,通過在一臺 i586- 133 的機器上測試是 10 微秒(運行 Linux),可見這種機制所提供的簡潔性、靈活性還是 值得的。但如果我們要追求高效率的話,比如在實時系統中就要盡可能的少用這種機制。
(2) 信號與槽機制與普通函數的調用一樣,如果使用不當的話,在程序執行時也有可能 產生死循環。因此,在定義槽函數時一定要注意避免間接形成無限循環,即在槽中再次發射 所接收到的同樣信號。
(3) 如果一個信號與多個槽相關聯的話,那么,當這個信號被發射時,與之相關的槽被 激活的順序將是隨機的,并且我們不能指定該順序。
(4) 宏定義不能用在 signal 和 slot 的參數中。
(5) 構造函數不能用在 signals 或者 slots 聲明區域內。
(6) 函數指針不能作為信號或槽的參數。
(7) 信號與槽不能有缺省參數。
(8) 信號與槽也不能攜帶模板類參數。
6\.小結
從 QObject 或其子類(例如 Qwidget)派生的類都能夠使用信號和槽機制。這種機制本身 是在 QObject 中實現的,并不只局限于圖形用戶界面編程中:當對象的狀態得到改變時, 它可以某種方式將信號發射(emit)出去,但它并不了解是誰在接收這個信號。槽被用于接收 信號,事實上槽是普通的對象成員函數。槽也并不了解是否有任何信號與自己相連接。而 且,對象并不了解具體的通信機制。這實際上是 “封裝”概念的生動體現,信號與槽機制 確保了 Qt 中的對象被當作軟件的組件來使用,體現了“軟件構件化”的思想。
- 第 1 章 走近 Qt
- 1.1 Qt 簡介
- 1.2 Qt 紀事概覽
- 1.3 Qt 套件的組成(以 Qt4.5 為準)
- 1.4 Qt 的授權
- 1.5 Qt 的產品
- 1.6 Qt 的服務與支持
- 1.7 Qt 的最新進展
- 1.8為什么選擇 Qt
- 1.9 問題與解答
- 1.10 總結與提高
- 第 2 章 Qt 的安裝與配置
- 2.1 獲取 Qt
- 2.2 協議說明
- 2.3 安裝 Qt
- 2.4 配置 Qt4 環境
- 2.5 問題與解答
- 2.6 總結與提高
- 第 3 章 Qt 編程基礎
- 3.1 標準 C++精講
- 3.2 Windows 編程基礎
- 3.3 Linux 編程基礎
- 3.4 Mac 編程基礎
- 3.5 問題與解答
- 3.6 總結與提高
- 第 4 章 Qt 4 集成開發環境
- 4.1 常見的 Qt IDE
- 4.2 Qt Creator
- 4.3 Eclipse
- 4.5 問題與解答
- 4.6 總結與提高
- 第 5 章 使用 Qt 基本 GUI 工具
- 5.1 使用 Qt Designer 進行 GUI 設計
- 5.2 使用 Qt Assistant 獲取在線文檔與幫助
- 5.3 使用 Qt Demo 學習 Qt 應用程序開發
- 5.4 問題與解答
- 5.5 總結與提高
- 第 6 章 Qt 4 程序開發方法和流程
- 6.1 開發方法
- 6.2 Hello Qt
- 6.3 幾個重要的知識點
- 6.4 問題與解答
- 6.5 總結與提高
- 第 7 章 對話框
- 7.1 QDialog 類
- 7.2 子類化 QDialog
- 7.3 快速設計對話框
- 7.4 常見內建(built in)對話框的使用
- 7.5 模態對話框與非模態對話框
- 7.6 問題與解答
- 7.7 總結與提高
- 第 8 章 主窗口
- 8.1 主窗口框架
- 8.2 創建主窗口的方法和流程
- 8.3 代碼創建主窗口
- 8.4 使用 Qt Designer 創建主窗口
- 8.5 中心窗口部件專題
- 8.6 Qt4 資源系統專題
- 8.7 錨接窗口
- 8.8 多文檔
- 8.9 問題與解答
- 8.10 總結與提高
- 第 9 章 Qt 樣式表與應用程序觀感
- 9.1 應用程序的觀感
- 9.2 QStyle 類的使用
- 9.3 樣式表概述
- 9.4 使用樣式表
- 9.5 問題與解答
- 9.6 總結與提高
- 第 10 章 在程序中使用.ui 文件
- 10.1 uic 的使用
- 10.2 Ui_YourFormName.h 文件的組成
- 10.3 編譯時加入處理.ui 文件的方法
- 10.4 運行時加入處理.ui 文件的方法
- 10.5 信號與槽的自動連接
- 10.6 問題與解答
- 10.7 總結與提高 本章主要講解了以下內容:
- 第 11 章 布局管理
- 11.1 基本概念和方法
- 11.2在 Qt Designer 中使用布局
- 11.3 基本布局實踐
- 11.4 堆棧布局
- 11.5 分裂器布局
- 11.6 自定義布局管理器
- 11.7 布局管理經驗總結
- 11.8 問題與解答
- 11.9 總結與提高
- 第 12 章 使用 Qt Creator
- 12.1 Qt Creator 概覽
- 12.2 Qt Creator 的組成
- 12.3 快捷鍵和常用技巧
- 12.4 Qt Creator 構建系統的設置
- 12.5 處理項目間依賴關系( Dependencies )
- 12.6 Qt 多版本共存時的管理
- 12.7 使用定位器在代碼間快速導航
- 12.8 如何創建一個項目
- 12.9 實例講解
- 12.10 使用 Qt Creator 調試程序
- 12.11 問題與解答
- 12.12 總結與提高
- 第 13 章 Qt 核心機制與原理
- 13.1 Qt 對標準 C++的擴展
- 13.2 信號與槽
- 13.3 元對象系統
- 13.4 Qt 的架構
- 13.5 Qt 的事件模型
- 13.6 構建 Qt 應用程序
- 13.7 總結與提高
- 附錄 A qmake 使用指南
- A.1 qmake 簡介
- A.2 使用 qmake
- 附錄 B make 命令
- B.1 命令解釋
- B.2 使用 make 自動構建
- 附錄 C Qt 資源
- C.1Qt 官方資源
- C.2 Qt 開發社區