歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Qt信號槽機制

Qt信號槽機制

日期:2017/3/1 9:19:55   编辑:Linux編程

信號槽

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

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

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

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

信號與槽

在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上,程序運行結果:

這個例子展示了對象之間通信的一種方式。對象間可以一起工作,而不需要知道彼此的任何信息。為了達到通信的目的,只需要將它們連接起來,而這只需要通過 調用 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會自動移除和這個對象的所有鏈接。

感謝您耐心的閱讀。

Qt入門學習——Qt 5 幫助文檔的使用 http://www.linuxidc.com/Linux/2015-09/123102.htm

Ubuntu 環境下Gtk與QT編譯環境安裝與配置 http://www.linuxidc.com/Linux/2013-08/88539.htm

Linux系統下QT環境搭建 http://www.linuxidc.com/Linux/2013-07/87576.htm

Ubuntu下QT控制台程序無法運行的解決方案以及XTerm的配置方法 http://www.linuxidc.com/Linux/2013-06/86244.htm

Ubuntu 10.04下QT4.7.4移植詳解 http://www.linuxidc.com/Linux/2013-01/77930.htm

Ubuntu 14.04下安裝部署Qt5開發環境 http://www.linuxidc.com/Linux/2014-05/101774.htm

Qt 的詳細介紹:請點這裡
Qt 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved