天天看點

QT信号槽機制[Qt槽判斷信号來源]信号槽信号與槽自定義信号和槽的一個例子細節

大家好,我是架構君,一個會寫代碼吟詩的架構師。今天說一說QT信号槽機制[Qt槽判斷信号來源],希望能夠幫助大家進步!!!

信号槽

信号槽是QT中用于對象間通信的一種機制,也是QT的核心機制。在GUI程式設計中,我們經常需要在改變一個元件的同時,通知另一個元件做出響應。例如:

QT信号槽機制[Qt槽判斷信号來源]信号槽信号與槽自定義信号和槽的一個例子細節

一開始我們的Find按鈕是未激活的,使用者輸入要查找的内容後,查找按鈕就被激活,這就是輸入框與Find按鈕這兩個元件間通信的例子。

QT信号槽機制[Qt槽判斷信号來源]信号槽信号與槽自定義信号和槽的一個例子細節

早期,對象間的通信采用回調來實作。回調實際上是利用函數指針來實作,當我們希望某件事發生時處理函數能夠獲得通知,就需要将回調函數的指針傳遞給處理函數,這樣處理函數就會在合适的時候調用回調函數。回調有兩個明顯的缺點:

  • 它們不是類型安全的,我們無法保證處理函數傳遞給回調函數的參數都是正确的。
  • 回調函數和處理函數緊密耦合,源于處理函數必須知道哪一個函數被回調。

信号與槽

在QT中,我們有回調技術之外的選擇,也即是信号槽機制。所謂的信号與槽,其實都是函數。當特定事件被觸發時(如在輸入框輸入了字元)将發送一個信号,而與該信号建立的連接配接槽,則可以接收到該信号并做出反應(激活Find按鈕)。

QT元件預定義了很多信号和槽,而在GUI程式設計中,我們習慣于繼承那些元件,繼承後添加我們自己的槽,以便以我們的方式來處理信号。槽和普通的C++成員函數幾乎是一樣的,它可以是虛函數,可以被重載,可以是共有、私有或是保護的,也同樣可以被其他成員函數調用。它的函數參數也可以是任意類型的。唯一不同的是:槽還可以和信号連接配接在一起。

與回調不同,信号槽機制是類型安全的。這展現在信号的函數簽名與槽的函數簽名必須比對上,才能夠發生信号的傳遞。實際上,槽的參數個數可以比信号的參數個數少,因為槽能夠忽略信号形參中多出來的參數。信号和槽是松耦合的:發出信号的類不關心哪些類将接收它的信号。QT的信号槽機制吧哦這裡在正确的時間,槽能夠接收到信号的參數并調用。信号和槽都可以有任意個數的參數,它們都是類型安全的。

自定義信号和槽的一個例子

首先我們要知道的是,所有繼承自QObject或者它的子類(如QWidget)都可以包含信号槽。我們寫的類須繼承自QObject(或其子類)。所有包含了信号槽的類都必須在聲明的上部含有Q_OBJECT宏。

一個基于QObject的C++簡單類:

//MyStr.h
# ifndef  MYSTR
# define  MYSTR
#include<QObject>
#include<QString>
 
class MyStr :public QObject
{
    Q_OBJECT //必須包含的宏
 
public:
    MyStr (){m_value = "zero";}
 
    QString value() const {return  m_value;}
 
public slots :
    void setValue(QString value );
 
    signals: //信号
    void valueChanged(QString newValue); 
private:
    QString m_value;
};
 
#endif            

複制

隻聽到從架構師辦公室傳來架構君的聲音:

新酒又添殘酒困。有誰來對上聯或下聯?

  • 在這個簡單的類中,我們可以看到,使用slots來表示槽,而使用signals來表示信号。實際上沒有那麼神秘,它們都是宏定義,甚至signals隻是public的宏定義:
#     define signals public           

複制

  • Signal的代碼會由 moc 自動生成,開發人員一定不能在自己的C++代碼中實作它。
  • 反之,槽應該由程式設計人員來實作,下面提供MyStr::setVaule()的一種可能實作
#include"MyStr.h"
void MyStr::setValue(QString value)
{
    if(value != m_value)
    {
        m_value = value;
        emit valueChanged(value);
    }
}           

複制

setValue函數首先比較新參的值與資料成員的值是否是一樣的(後面有解釋為何這樣做),如果不是,則設定好資料成員m_value的值,然後,把信号valueChanged()發送出去。發送給誰?類并沒有寫,這并不是類設計者所關心的,也不是類所關心的,它隻管把信号發送出去就行。然後,我們再來設定誰來接收這個信号。

int main(int argc, char *argv[])
{
    MyStr a;
    MyStr b;
    QObject::connect(&a,SIGNAL(valueChanged(QString)),&b,SLOT(setValue(QString)));
    a.setValue("this is A");
    return 0;
}           

複制

我們定義了兩個類對象a/b,使用 QObject::connect()函數指定了發送方、信号、接收方、槽等資訊,connect函數的格式如下:

QObject::connect(    發送方,    SIGNAL(...),    接收方,    SLOT(..)    );           

複制

當我們調用a的成員函數setValue時,該函數除了把a.m_value設定為"this is A",也把信号valueChanged()發送出去,被b.setValue所接收,進而,把b.m_value設定為"this is A",同時b.setValue又把valueChanged信号發射出去,然而該信号并沒有對象接收,因為我們沒有建立以b為發送方的任何連接配接。此時你應該明白,為何在emit前需要判斷value != m_value,因為如果沒有此步驟,且恰巧設定了

QObject::connect(&b,SIGNAL(valueChanged(QString)),&a,SLOT(setValue(QString)));

則b的信号被a接收,a又發送信号被b接收,如此進入死循環。

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
 
    MyStr  a;
    MyStr  b;
    QObject::connect(&a,SIGNAL(valueChanged(QString)),&b,SLOT(setValue(QString)));
    a.setValue("this is A");
 
    QLabel* label = new QLabel;
    label->setText( b.value());
    label->show();
    return app.exec();
}           

複制

我們使用label輸出來看看b是否接收到a的信号,如果是,則b的内容應該是"this is A",輸出在label上,程式運作結果:

QT信号槽機制[Qt槽判斷信号來源]信号槽信号與槽自定義信号和槽的一個例子細節

這個例子展示了對象之間通信的一種方式。對象間可以一起工作,而不需要知道彼此的任何資訊。為了達到通信的目的,隻需要将它們連接配接起來,而這隻需要通過 調用 QObject::connect() 函數指定一些簡單資訊就好。

細節

連接配接

要把信号成功連接配接到槽,它們的參數必須具有相同的順序和相同的類型,或者允許信号的參數比槽多,槽會自動忽略掉多出來的參數而進行調用。

一個信号可以連接配接多個槽

使用QObject::connect可以把一個信号連接配接到多個槽,而當信号發射時,将按聲明聯系時的順序依次調用槽。

MyStr  a;
 MyStr  b;
 MyStr  c;
//信号連接配接到兩個槽
 QObject::connect(&a,SIGNAL(valueChanged(QString)),&b,SLOT(setValue(QString)));
 QObject::connect(&a,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
 a.setValue("this is A");
//依次調用b.setValue()、c.setValue()           

複制

多個信号可以連接配接同一個槽

同樣的,可以讓多個信号連接配接到同一個槽上 ,而且其中的每一個信号的發送,都會調用了那個槽。

MyStr  a;
 MyStr  b;
 MyStr  c;
//兩個信号連接配接到同一個槽
 QObject::connect(&a,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
 QObject::connect(&b,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
//下面的操作皆會調用到槽c.setValue()
 a.setValue("this is A");
b.setValue("this is B");           

複制

一個信号可以和另外一個信号相連接配接

當發射第一個信号的時候,也會把第二個信号一個發送出去。

MyStr  a;
 MyStr  b;
 MyStr  c;
//兩個信号相連接配接
 QObject::connect(&a,SIGNAL(valueChanged(QString)),&b,SIGNAL(valueChanged(QString)));
//再建立b與c的連接配接
 QObject::connect(&b,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
//下面的操作同時發送了信号a.valueChanged與b.valueChanged
 a.setValue("this is A");
//進而信号b.valueChanged被槽c.setValue所接收           

複制

連接配接可以被移除

//移除b 與 c之間的連接配接
  QObject::disconnect(&b,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));           

複制

實際上當對象被delete時,其關聯的所有連結都會失效,QT會自動移除和這個對象的所有連結。

感謝您耐心的閱讀。

今天文章到此就結束了,感謝您的閱讀,Java架構師必看祝您升職加薪,年年好運。