【Qt】Qt中信号与槽
00. 目錄
文章目錄
- 00. 目錄
- 01. 信號與槽
- 02. 介紹
- 03. 信號與槽
- 04. 信號
- 05. 槽
- 06. 一個小例子
- 07. 一個真實的例子
- 08. 信號和槽使用默認參數(shù)
- 09. 信號與槽高級用法
- 10. 使用Qt與第三方信號和插槽
01. 信號與槽
? 信號和槽用于對象之間的通信。信號和插槽機制是Qt的核心功能,可能是與其他框架提供的功能最不同的部分。Qt的元對象系統(tǒng)使信號和槽成為可能。
02. 介紹
? 在GUI編程中,當我們更改一個小部件時,我們經(jīng)常需要通知另一個小部件。更一般地說,我們希望任何類型的對象能夠彼此通信。例如,如果用戶單擊“ **關閉”**按鈕,我們可能希望調用窗口的close()函數(shù)。
? 其它工具包使用回調實現(xiàn)這種通信。回調是指向函數(shù)的指針,因此如果您希望處理函數(shù)通知您某些事件,則將指針傳遞給處理函數(shù)的另一個函數(shù)(回調)。然后,處理函數(shù)在適當時調用回調。雖然確實存在使用此方法的成功框架,但回調可能不直觀,并且可能在確保回調參數(shù)的類型正確性方面存在問題。
03. 信號與槽
? 在Qt中,我們有一種替代回調技術:我們使用信號和槽。發(fā)生特定事件時會發(fā)出信號。Qt的小部件有許多預定義信號,但我們總是可以將部件子類化為了向它們添加我們自己的信號。槽是響應于特定信號而被調用的函數(shù)。Qt的部件中有許多預定義的槽函數(shù),但通常的做法是將部件子類化并添加自己的槽,以便您可以處理您感興趣的信號。
? 信號和槽機制是類型安全的:信號的簽名必須與接收槽的簽名匹配。(事實上,一個槽可能比它收到的信號具有更短的簽名,因為它可以忽略額外的參數(shù)。)由于簽名是兼容的,編譯器可以幫助我們在使用基于函數(shù)指針的語法時檢測類型不匹配。基于字符串的SIGNAL和SLOT語法將在運行時檢測類型不匹配。信號和槽是松散耦合:發(fā)出信號的類既不知道也不用關心哪個槽接收該信號。Qt的信號和槽機制確保如果您將信號連接到槽,將在適當?shù)臅r間使用信號的參數(shù)調用槽。信號和槽可以采用任何類型的任意數(shù)量的參數(shù)。它們完全是類型安全的。
? 從QObject或其子類之一(例如,QWidget)繼承的所有類都可以包含信號和槽。當它們以某種方式改變它的狀態(tài)時, 信號就會被發(fā)送, 其它對象可能對該信號感興趣。這是的話所有的對象都可以通訊。它不知道或關心是否有任何東西正在接收它發(fā)出的信號。這是真正的信息封裝,并確保該對象可以用作軟件組件。
? 槽可用于接收信號,但它們也是普通的成員函數(shù)。就像一個對象不知道是否有任何東西接收到它的信號一樣,一個槽函數(shù)不知道它是否有任何信號連接到它。這確保了可以使用Qt創(chuàng)建真正獨立的組件。
? 可以將任意數(shù)量的信號連接到一個槽,并且可以根據(jù)需要將信號連接到任意數(shù)量的插。甚至可以將信號直接連接到另一個信號。(每當發(fā)射第一個信號時,將立即發(fā)出第二個信號。)
信號和插共同構成了一個強大的組件編程機制。
04. 信號
? 當對象的內部狀態(tài)以某種可能對對象的客戶端或所有者感興趣的方式發(fā)生更改時,對象會發(fā)出信號。信號是公共訪問函數(shù),可以從任何地方發(fā)出,但我們建議只從定義信號及其子類的類中發(fā)出它們。
? 當信號發(fā)出時,通常會立即執(zhí)行與其連接的槽函數(shù),就像正常的函數(shù)調用一樣。發(fā)生這種情況時,信號和槽機制完全獨立于任何GUI事件循環(huán)。emit發(fā)出信號之后, 所有與它關聯(lián)的槽函數(shù)將會被執(zhí)行。這種情形與使用排隊連接時情況略有不同; 在這種情況下,emit關鍵字后面的代碼將立即繼續(xù),并且稍后將執(zhí)行槽函數(shù)。
? 如果多個槽連接到一個信號,則在發(fā)出信號時,槽函數(shù)將按照它們已連接的順序依次執(zhí)行。
? 信號由moc自動生成,不得在.cpp文件中實現(xiàn)。它們永遠不會有返回類型(即使用void)。
關于參數(shù)的說明
我們的經(jīng)驗表明,如果信號和槽不使用特殊類型,則它們可以重復使用。如果QScrollBar :: valueChanged()使用特殊類型,例如假設的QScrollBar :: Range,則它只能連接到專門為QScrollBar設計的槽函數(shù)。將不同的輸入控件連接在一起是不可能的。
05. 槽
? 當連接的信號被發(fā)送時, 對應的槽函數(shù)將會被執(zhí)行。槽函數(shù)是普通的C ++函數(shù),可以正常調用; 它們唯一的特點是信號可以連接到它們。
? 由于插槽是普通的成員函數(shù),因此它們在直接調用時遵循正常的C ++規(guī)則。但是,作為槽,它們可以通過信號和槽連接由任何組件調用,而不管其訪問級別如何。這意味著從任意類的實例發(fā)出的信號可以導致在不相關的類的實例中調用私有槽。
? 我們發(fā)現(xiàn)這些槽在實踐中非常有用時, 還可以將槽函數(shù)定義為虛槽函數(shù)。
? 與回調相比,信號和槽稍微慢一些,因為它們提供了更大的靈活性,盡管實際應用的差異是微不足道的。通常,發(fā)射連接到某些槽的信號比使用非虛函數(shù)直接調用槽函數(shù)大約慢十倍。這是定位連接對象,安全地遍歷所有連接(即檢查后續(xù)槽函數(shù)在發(fā)射期間沒有被銷毀)以及以通用方式編組任何參數(shù)所需的開銷。雖然十個非虛函數(shù)調用可能聽起來很多,但它的開銷比任何new或delete操作都少得多,例如。只要執(zhí)行場景后面的字符串,vector或list操作,就需要new或delete,信號和槽開銷僅僅是函數(shù)調用開銷的一小部分。無論何時在槽函數(shù)中進行系統(tǒng)調用,情況都是如此; 或間接調用十多個函數(shù)。由于信號和槽機制的簡單性和靈活性, 這一點開銷是非常值得的,用戶甚至都不會注意到。
? 請注意,定義變量的其他庫在與基于Qt的應用程序一起編譯時調用signals或slots可能導致編譯器警告和錯誤。要解決這個問題,#undef違規(guī)的預處理器符號。
06. 一個小例子
最小的C ++類聲明可能是:
class Counter{public:Counter() { m_value = 0; }int value() const { return m_value; }void setValue(int value);private:int m_value;};一個小的基于QObject的類可能是:
#include <QObject>class Counter : public QObject{Q_OBJECTpublic:Counter() { m_value = 0; }int value() const { return m_value; }public slots:void setValue(int value);signals:void valueChanged(int newValue);private:int m_value;};? 基于QObject的版本具有相同的內部狀態(tài),并提供訪問狀態(tài)的公共方法,但此外它還支持使用信號和槽進行組件編程。這個類可以通過發(fā)出信號告訴外面世界它的狀態(tài)已經(jīng)改變了valueChanged(),并且它有一個其它對象可以發(fā)送信號的槽。
? 包含信號或槽的所有類必須在其聲明的頂部提及Q_OBJECT。它們還必須(直接或間接)從QObject派生。
? 槽函數(shù)由應用程序員實現(xiàn)。以下是Counter::setValue()槽函數(shù)的可能實現(xiàn):
void Counter::setValue(int value){if (value != m_value) {m_value = value;emit valueChanged(value);}}? 該emit行發(fā)出valueChanged()來自對象的信號,新值作為參數(shù)。
? 在下面的代碼片段中,我們創(chuàng)建了兩個Counter對象,并使用QObject :: connect()將第一個對象的valueChanged()信號連接到第二個對象的setValue()槽:
Counter a, b;QObject::connect(&a, &Counter::valueChanged,&b, &Counter::setValue);a.setValue(12); // a.value() == 12, b.value() == 12b.setValue(48); // a.value() == 12, b.value() == 48? 調用a.setValue(12)導致對象a發(fā)出一個valueChanged(12)信號,該信號對象比b將會接收, 并且執(zhí)行槽函數(shù)setValue(),即被b.setValue(12)調用。然后b發(fā)射相同valueChanged()的信號,但由于沒有槽函數(shù)已經(jīng)被連接到b的valueChanged()`信號,該信號被忽略。
請注意
該setValue()函數(shù)僅設置值并發(fā)出信號value != m_value。這可以防止在循環(huán)連接的情況下無限循環(huán)(例如,如果b.valueChanged()連接到a.setValue())。
? 默認情況下,對于您所做的每個連接,都會發(fā)出一個信號; 發(fā)出兩個信號用于重復連接。您可以通過一次disconnect()調用來中斷所有這些連接。如果傳遞Qt :: UniqueConnection 類型,則只有在不是重復的情況下才會建立連接。如果已經(jīng)有重復(完全相同的信號到相同對象上的完全相同的槽),連接將失敗并且connect將返回false
? 此示例說明對象可以一起工作,而無需了解彼此的任何信息。為了實現(xiàn)這一點,對象只需要被連接在一起,并且這可以用一些簡單的實現(xiàn)的QObject ::連接()函數(shù)調用,或者與uic的自動連接功能。
07. 一個真實的例子
這是一個小部件的簡單評論示例。
#ifndef LCDNUMBER_H#define LCDNUMBER_H#include <QFrame>class LcdNumber : public QFrame{Q_OBJECTLcdNumber通過QFrame和QWidget繼承具有大部分信號槽知識的QObject。它有點類似于內置的QLCDNumber小部件。
? Q_OBJECT宏由預處理器擴展來聲明由moc實現(xiàn)的幾個成員函數(shù); 如果您在“未定義的vtable引用”中遇到編譯器錯誤LcdNumber,您可能忘記運行moc或在鏈接命令中包含moc輸出。
public:LcdNumber(QWidget *parent = 0);? 它與moc顯然不相關,但是如果你繼承了QWidget,你幾乎肯定希望parent在構造函數(shù)中使用該參數(shù)并將其傳遞給基類的構造函數(shù)。
? 這里省略了一些析構函數(shù)和成員函數(shù); 該moc忽略成員函數(shù)。
signals:void overflow();? 當要求顯示不可能的值時LcdNumber發(fā)出信號。
? 如果您不關心溢出,或者您知道不會發(fā)生溢出,則可以忽略該overflow()信號,即不要將其連接到任何槽。
? 另一方面,如果要在數(shù)字溢出時調用兩個不同的錯誤函數(shù),只需將信號連接到兩個不同的槽即可。Qt將調用它們(按照它們連接的順序)。
public slots:void display(int num);void display(double num);void display(const QString &str);void setHexMode();void setDecMode();void setOctMode();void setBinMode();void setSmallDecimalPoint(bool point);};#endif? 槽是一種接收函數(shù),用于獲取有關其他小部件中狀態(tài)更改的信息。LcdNumber如上面的代碼所示,使用它來設置顯示的數(shù)字。由于display()該類是與該程序其余部分的接口的一部分,因此該槽是公共的。
? 有幾個示例程序將QScrollBar的valueChanged()信號連接到插槽,因此LCDNumber會連續(xù)顯示滾動條的值。
? 注意display()重載; 當您將信號連接到槽時,Qt將選擇適當?shù)陌姹尽J褂没卣{,您必須找到五個不同的名稱并自己跟蹤類型。
? 此示例中省略了一些不相關的成員函數(shù)。
08. 信號和槽使用默認參數(shù)
信號和槽的簽名可以包含參數(shù),參數(shù)可以具有默認值。考慮QObject :: destroyed():
void destroyed(QObject* = 0);當QObject的被刪除時,它發(fā)射這個 QObject::destroyed()信號。我們希望捕獲這個信號,無論我們對刪除的QObject有何懸空引用,我們都可以清理它。合適的插函數(shù)可能是:
void objectDestroyed(QObject* obj = 0);要將信號連接到槽,我們使用QObject :: connect()。有幾種方法可以連接信號和插槽。第一個是使用函數(shù)指針:
connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);將QObject :: connect()與函數(shù)指針一起使用有幾個優(yōu)點。首先,它允許編譯器檢查信號的參數(shù)是否與槽的參數(shù)兼容。如果需要,編譯器也可以隱式轉換參數(shù)。
您還可以連接到仿函數(shù)或C ++ 11 lambdas:
connect(sender, &QObject::destroyed, [=](){ this->m_objects.remove(sender); });將信號連接到槽的另一種方法是使用QObject :: connect()和SIGNAL和SLOT宏。規(guī)則是否包含參數(shù)在SIGNAL()和SLOT()宏中,如果參數(shù)有默認值,是傳遞給簽名SIGNAL()宏必須不能比傳遞到SLOT()宏簽名參數(shù)少。
所有這些都可行
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*))); connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed())); connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));但這一個不起作用:
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed(QObject*)));因為槽將期望信號不會發(fā)送的QObject。此連接將報告運行時錯誤。
請注意,使用此QObject :: connect()重載時,編譯器不會檢查signal和slot參數(shù)。
09. 信號與槽高級用法
? 對于您可能需要有關信號發(fā)送者的信息的情況,Qt提供QObject :: sender()函數(shù),該函數(shù)返回指向發(fā)送信號的對象的指針。
? 所述QSignalMapper類提供了一種用于有許多信號被連接到相同的槽和槽需要不同的方式處理每一個信號的情況。
? 假設您有三個按鈕,用于確定要打開的文件:“稅務文件”,“帳戶文件”或“報告文件”。
? 要打開正確的文件,可以使用QSignalMapper :: setMapping()將所有QPushButton :: clicked()信號映射到QSignalMapper對象。然后將文件的QPushButton :: clicked()信號連接到QSignalMapper :: map()槽。
signalMapper = new QSignalMapper(this);signalMapper->setMapping(taxFileButton, QString("taxfile.txt"));signalMapper->setMapping(accountFileButton, QString("accountsfile.txt"));signalMapper->setMapping(reportFileButton, QString("reportfile.txt"));connect(taxFileButton, &QPushButton::clicked,signalMapper, &QSignalMapper::map);connect(accountFileButton, &QPushButton::clicked,signalMapper, &QSignalMapper::map);connect(reportFileButton, &QPushButton::clicked,signalMapper, &QSignalMapper::map);? 然后,將mapped()信號連接到readFile()將打開其他文件的位置,具體取決于按下的按鈕。
connect(signalMapper, SIGNAL(mapped(QString)),this, SLOT(readFile(QString)));10. 使用Qt與第三方信號和插槽
可以將Qt與第三方信號/插槽機制一起使用。您甚至可以在同一個項目中使用這兩種機制。只需將以下行添加到qmake項目(.pro)文件即可。
CONFIG += no_keywords它告訴Qt的不要定義moc關鍵字signals,slots和emit,因為這些名稱將由第三方庫可以使用,例如boost。然后繼續(xù)使用帶有no_keywords標志的Qt信號和插槽,只需將源中Qt moc關鍵字的所有使用替換為相應的Qt宏Q_SIGNALS(或Q_SIGNAL),Q_SLOTS(或Q_SLOT)和Q_EMIT。
另請參閱元對象系統(tǒng)和Qt的屬性系統(tǒng)。
總結
以上是生活随笔為你收集整理的【Qt】Qt中信号与槽的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Tools】C/C++开发SDK下载汇
- 下一篇: 【Qt】Qt学习资料汇总