Qt對象模型之一:信號和槽


一、信號和槽機制概述

信號槽是 Qt 框架引以為豪的機制之一。所謂信號槽,實際就是觀察者模式。當某個事件發生之后,比如,按鈕檢測到自己被點擊了一下,它就會發出一個信號(signal)。這種發出是沒有目的的,類似廣播。如果有對象對這個信號感興趣,它就會使用連接(connect)函數,意思是,將想要處理的信號和自己的一個函數(稱為槽(slot))綁定來處理這個信號。也就是說,當信號發出時,被連接的槽函數會自動被回調

槽的本質是類的成員函數,其參數可以是任意類型的。和普通C++成員函數幾乎沒有區別,它可以是虛函數;也可以被重載;可以是公有的、保護的、私有的、也可以被其他C++成員函數調用。唯一區別的是:槽可以與信號連接在一起,每當和槽連接的信號被發射的時候,就會調用這個槽。

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

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


二、典型應用示例

下面通過一個簡單的例子來進一步講解信號和槽的相關知識。新建Qt Gui應用,項目名稱為mySignalSlot,基類選擇QWidget,然后類名保持Widget不變。完成后首先在widget.h文件中聲明信號和槽:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QDebug>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT //必須包含的宏

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

public slots:
    void testSolts(); //測試槽函數

signals:
    void testSignals(); //測試信號

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H

使用信號和槽還必須在類聲明 的最開始處添加Q_OBJECT宏,在這個程序中,類的聲明是自動生成的,已經添加了這個宏。



signal的代碼會由 moc 自動生成,開發人員不能在自己的C++代碼中實現它。反之,槽應該由開發人員來實現。修改widget.cpp文件如下:

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    //連接信號槽
    connect(this,SIGNAL(testSignals()),this,SLOT(testSolts()));
    //發射信號
    emit testSignals();
}

Widget::~Widget()
{
    delete ui;
}

//測試槽函數
void Widget::testSolts()
{
    qDebug() << "調用了測試槽函數";
}

這個例子實現的效果是:發射信號后,會調用testSolts()槽函數,則“應用程序輸出”窗口會打印出“調用了測試槽函數”。



三、使用信號和槽的注意事項

下面列舉一下使用信號和槽應該注意的注意事項:

  • 發送者和接收者都需要繼承自QObject或其子類;
  • 必須在類聲明的最開始添加Q_OBJECT宏;
  • 槽中的參數類型要和信號的參數的類型相對應,且不能比信號的參數多;
  • 使用 signals 標記信號函數,信號是一個函數聲明,返回 void,不需要實現函數代碼;
  • 使用 emit 在恰當的位置發送信號;
  • 槽函數是普通的成員函數,作為成員函數,會受到 public、private、protected 的影響;
  • 任何成員函數、static 函數、全局函數和 Lambda 表達式都可以作為槽函數。


四、信號和槽的關聯

信號和槽進行關聯使用的是QObject類的connect()函數,Qt4中這個函數的原型如下:

static QMetaObject::Connection QObject::connect(const QObject *sender, 
										const char *signal,
                        				const QObject *receiver, 
                        				const char *member, 
                        		Qt::ConnectionType = Qt::AutoConnection);

第一個參數sender為發送信號的對象,第二個參數signal為要發送的信號,第三個參數receiver為接收信號的對象,第4個參數slot為接收對象在接收到信號之后所需要調用的槽函數。connect()函數的最后一個參數表明了關聯的方式,默認值是Qt::AutoConnection。對於信號和槽,必須使用SIGNAL()和SLOT()宏,它們可以將其參數轉化為const char*類型。connect()函數的返回值為bool類型,當關聯成功時返回true。

對於信號和槽的參數問題,基本原則是信號中的參數類型要和槽中的參數類型相對應,而且信號中的參數可以多於槽中的參數,但是不能反過來;如果信號中有多余的參數,那么它們將被忽略。



Qt5中connect()函數新加入的一種重載形式如下:

static QMetaObject::Connection QObject::connect(const QObject *sender, 
										PointerToMemberFunction signal,
                        				const QObject *receiver, 
                        				PointerToMemberFunction member, 
                        		Qt::ConnectionType type = Qt::AutoConnection);

與Qt4最大的不同就是,指定信號和槽兩個參數時可以不用再使用SIGNAL()和SLOT()宏,並且槽函數不再必須是使用slots關鍵字聲明的函數,而可以是任意能和信號關聯的成員函數。要使一個成員函數可以和信號關聯,那么這個函數的參數數目不能超過信號的參數數目,但是並不要求該函數擁有的參數類型與信號中對應的參數類型完全一致,只需要可以進行隱式轉換即可。使用這種重載形式,前面程序中的關聯可以使用以下代碼代替:

connect(this,&Widget::testSignals,this,&Widget::testSolts);

使用這種方式與前一種相比,還有一個好處就是可以在編譯時進行檢查,信號與槽的拼寫錯誤、參函數參數數目多於信號的參數數目等錯誤在編譯時就能夠發現。所以建議在編寫Qt5代碼時使用這種關聯形式。



信號和槽還有一種自動關聯方式,例如在設計模式直接生成的按鈕的單擊信號的槽,就是使用的這種方式: on_pushButton_clicked() 由字符串on、部件的objectName和信號名稱這三部分組成,中間用下划線隔開。這樣形式命名的槽可以直接和信號關聯,不用再使用connect()函數,不過使用這種方式還要進行其他設置。



五、斷開信號和槽的關聯

可以通過disconnect()函數來斷開信號和槽的關聯,其原型如下:

static bool QObject::disconnect(const QObject *sender, const char *signal,
                           const QObject *receiver, const char *member);

(1)斷開與一個發送對象所有信號的所有關聯

disconnect(myObject, nullptr, nullptr, nullptr);

等價於

myObject->disconnect();

(2)斷開與一個指定信號的所有關聯

disconnect(myObject, SIGNAL(mySignal()), nullptr, nullptr);

等價於

myObject->disconnect(SIGNAL(mySignal()));

(3)斷開與一個指定接受對象的所有關聯

disconnect(myObject, nullptr, myReceiver, nullptr);

等價於

myObject->disconnect(myReceiver);

(4)斷開一個指定信號和槽的關聯

disconnect(myObject, SIGNAL(mySignal()), myReceiver, SLOT(mySlot()));

等價於

myObject->disconnect(SIGNAL(mySignal()), myReceiver, SLOT(mySlot()));

也等價於

disconnect(myConnection); //myConnection是進行關聯時connect()的返回值

其用法類似,只是其信號、槽參數需要使用函數指針 &MyObject::mySignal()、&MyReceiver::mySlot() 等形式。這個函數並不能斷開信號與一般函數或者lambda表達式之間的關聯,如果有這方面需要,則可以使用connect()返回值進行斷開。實際上當對象被delete時,其關聯的所有鏈接都會失效,QT會自動移除和這個對象的所有鏈接。



六、信號和槽的更多用法

(1)一個信號可以連接多個槽

使用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()

(2)多個信號可以連接同一個槽

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

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");

(3)一個信號可以和另外一個信號相連接

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

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所接收



七、擴展:Qt對象模型概述

標准C+ +對象模型可以在運行時非常有效地支持對象范式(object paradigm), 但是它的靜態特性在一些問題領域中不夠靈活。圖形用戶界面編程不僅需要運行時的髙效性,還需要高度的靈活性。為此,Qt在標准C+ +對象模型的基礎上添加了一些特性,形成了自己的對象模型。這些特性有:

  • 一個強大的無縫對象通信機制 ---- 信號和槽(signals and slots);
  • 可查詢和可設計的對象屬性系統(object properties);
  • 強大的事件和事件過濾器(events and event filters);
  • 通過上下文進行國際化的字符串翻譯機制(string translation for international­ization) ;
  • 完善的定時器(timers)驅動,使得可以在一個事件驅動的GUI中處理多個任務;
  • 分層結構的、可査詢的對象樹(object trees),它使用一種很自然的方式來組織對象擁有權(object ownership);
  • 守衛指針即QPointer,它在引用對象被銷毀時自動將其設置為0;
  • 動態的對象轉換機制(dynamic cast)。

Qt的這些特性都是在遵循標准C+ +規范內實現的,使用這些特性都必須繼承自 QObject類。其中對象通信機制和動態屬性系統還需要元對象系統(Meta-Object Sys­tem) 的支持。關於對象模型,可以在幫助中查看 Object Model 關鍵字。



參考:

C++框架_之Qt的信號和槽的詳解

QT信號槽機制



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM