QT5訊號槽個人總結
一、關於qt訊號槽的理解
這裡沿用豆子大神的話說,所謂訊號槽,實際就是觀察者模式。當某個事件發生之後,比如,按鈕檢測到自己被點選了一下,它就會發出一個訊號(signal)。這種發出是沒有目的的,類似廣播。如果有物件對這個訊號感興趣,它就會使用連線(connect)函式,意思是,用自己的一個函式(成為槽(slot))來處理這個訊號。也就是說,當訊號發出時,被連線的槽函式會自動被回撥。這就類似觀察者模式:當發生了感興趣的事件,某一個操作就會被自動觸發。
備註:強烈推薦豆子大神的Qt學習之路
提示:以下個人淺見,講的過於直白,大佬繞道。
二、Qt5訊號槽基本使用
在Qt5中,QObject::connect()有五個過載:
QMetaObject::Connection connect(const QObject *, const char *,
const QObject *, const char *,
Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, const QMetaMethod &,
const QObject * , const QMetaMethod &,
Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, const char *,
const char *,
Qt::ConnectionType) const;
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
const QObject *, PointerToMemberFunction,
Qt::ConnectionType)
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
Functor);
對五個過載進行分析:
1.函式返回值都是QMetaObject::Connection型別,這個返回值一般很少有用到,關於返回值的意義qt官方文件的說明是:
Returns a handle to the connection that can be used to disconnect it later.
返回該連線的控制代碼,以後可用於斷開連線。也就是說我們可以手動斷開這個connect,我們繫結widget的Signal_DoSomeThing訊號到widget的slot_toDo槽,然後當槽函式執行完成後手動斷開連線,虛擬碼如下:
auto connection = QObject::connect(this, &MyWidget::Signal_DoSomeThing, this, &MyWidget::slot_toDo);//auto:自動型別推斷,見c++11特性
emit Signal_DoSomeThing();
disconnect(connection ); //斷開連線的時候再發出訊號無法再觸發槽函式
顯然一般情況下我們不會這麼做,這種應用場景一般結合c++11的lambda 表示式應用,我們將上句程式碼進行變形:
void func1()
{
int para = 1;
auto connection = QObject::connect(this, &MyWidget::Signal_DoSomeThing, [this, ¶](){
//ToDo
});
emit Signal_DoSomeThing();
disconnect(connection ); //斷開連線的時候再發出訊號無法再觸發槽函式
}
void func2()
{
QString para = "a";
auto connection = QObject::connect(this, &MyWidget::Signal_DoSomeThing, [this, ¶](){
//ToDo
});
emit Signal_DoSomeThing();
disconnect(connection ); //斷開連線的時候再發出訊號無法再觸發槽函式
}
這樣我們就可以根據需求靈活變更,使用完成後切斷當前連線,程式碼耦合性更好。
似乎我們按照我們的想法實現了它該有的功能,但實際使用這根本不需要訊號槽連線,直接調函式不就完了,顯然不應該是這種連線完後馬上就去發訊號執行槽這種做法,而是由外部觸發這個訊號,那外部怎麼觸發這個訊號呢,必然是函式內執行了某個操作,執行完畢後觸發這個訊號,然後再執行即時繫結的槽,而這個函式內執行的某個操作,必然是應用於多執行緒的,如果是單執行緒的,那也就直接調函數了,還需要訊號槽嗎。
梳理一下,我們的應用邏輯應該是繫結一個訊號槽,然後執行一個執行緒操作,執行緒執行完畢後觸發訊號,執行即時繫結槽,執行完畢後斷開訊號槽連線。那這裡就涉及到一個問題,我們需要等待槽函式執行之後再斷開連線,槽函式執行又需要等待執行緒執行完畢觸發訊號後再執行,為了不影響主執行緒重新整理,QEventLoop很好的解決了這個問題。
到這裡就比較清晰了,舉一個典型例子,我們在給伺服器發訊息的時候,希望即時收到回覆並作出相應處理,虛擬碼如下:
void getMsgFromServerAndToDo(QString msg)
{
QString para = "a";
QEventLoop eventLoop;
auto connection = QObject::connect(this, &MyWidget::Signal_ResieveMsg, [this, ¶, &eventLoop](const QString &msgResult){
//ToDo
...
eventLoop.quit(); //退出事件迴圈
});
sendMsg(msg); //傳送訊息後進入事件迴圈,等待接收到訊息後觸發Signal_ResieveMsg
eventLoop.exec();
disconnect(c);
}
結合這個例子來看,是不是這種設計就非常巧妙,那這個para有什麼用?當然是為接收到的資料處理用啊,比如你要比對本地與伺服器資料,比如你要結合伺服器資料儲存到資料結構。
2.訊號槽的連線引數中,sender 和receiver 型別是const QObject *,只有signal 和slot的型別不一樣,
分析第一個,signal和slot的型別的型別都是const char *,Qt4中也包含了同樣的過載,因此,這種方式Qt4和Qt5是相容的:
QObject::connect(button, SIGNAL(clicked()),
this, SLOT(slot_toDo()));
SIGNAL和SLOT這兩個巨集,將兩個函式名轉換成了字串,訊號加字首2,槽加字首1。它與以下程式碼是等效的:
connect(button,”2clicked()”, this, ”1slot_toDo()”);
分析第二種,signal和slot的型別的型別都是const QMetaMethod &,我們可以將每個函式看做是QMetaMethod的子類。因此,這種寫法可以使用QMetaMethod進行型別比對。
分析第三種,缺少了 receiver。這個函式其實是將 this 指標作為 receiver。
分析第四種,signal 和 slot 型別則是PointerToMemberFunction。看這個名字就應該知道,這是指向成員函式的指標,而Qt4無此過載,這也解釋了為什麼Q5可以使用如下形式而Qt4不可以:
QObject::connect(button, &QPushButton::clicked,
this, &Qwidget::slot_toDo);
分析第5種,最後一個引數是Functor型別,Functor型別是什麼?也就是C++中的仿函式,至於什麼是仿函式這裡不作解釋,而Functor型別,使得槽函式可以接受任何成員函式、static 函式、全域性函式以及 Lambda 表示式。
######先寫這麼多…