qt5中信號和槽的新語法


qt5中的連接

有下列幾種方式可以連接到信號上

舊語法

qt5將繼續支持舊的語法去連接,在QObject對象上定義信號和槽函數,及任何繼承QObjec的對象(包含QWidget)。

connect(sender, SIGNAL (valueChanged(QString,QString)),receiver, SLOT (updateValue(QString)) );

新語法:連接到QObject成員

下面是一種新的方式來連接兩個QObjects:

connect(sender, &Sender::valueChanged,receiver, &Receiver::updateValue ); 

它支持:

  • 編譯期間檢查信號和槽是否存在,它們的類型,及Q_OBJECT是否丟失
  • 參數能被typedef或不同命名空間指定。
  • 如果有隱式轉換的參數,會自動轉換類型。比如QString到QVariant
  • 它可以連接QObject的任何成員方法,不僅僅是定義的槽。

它不支持:

  • 更復雜的語法?你需要指定你的對象類型、
  • 非常復雜的語法,比如重載,參見后面。
  • 在槽的中默認參數不在被支持。

新語法:連接到簡單的函數

新語法甚至能連接到函數,不僅僅是QObjects:

connect(sender, &Sender::valueChanged, someFunction);

支持:

  • 能和tr1::bind一起使用:

      connect(sender, &Sender::valueChanged,
        tr1::bind(receiver, &Receiver::updateValue, "senderValue", tr1::placeholder::_1));
    
  • 能和c++ 11 lambda表達式一起使用:

      connect(sender, &Sender::valueChanged, [=](const QString &newValue) {
        receiver->updateValue("senderValue", newValue);
      });
    

不支持:

當receiver被銷毀時,新語法不能自動斷開信號和槽的連接。 因為它是沒有跟QObject一起的偽函數。不管怎樣,從5.2版本開始有一個重載函數,它添加一個上下文對象,當對象摧毀時,這個連接會破壞。這個上下文也被使用在線程關聯性上: 這個lambda方法將會被調用在對象事件循環的線程中。

qt5中斷開連接

如你可能預期的那樣,在qt5中如何終止連接也會有一些新變化。

舊方式

你仍可以舊方式斷開連接(使用SIGNAL, SLOT方式)。但僅限是

  • 你使用舊方式連接,或者
  • 如果你想使用通配符,從指定的信號中斷開所有的槽

對稱的函數指針

disconnect(sender, &Sender::valueChanged,
 receiver, &Receiver::updateValue );

這只可以用在你使用同樣方式的連接上,或者你也可以使用0作為通配。 在實際中,它也不適用於靜態函數,仿函數,或lambda函數。

使用QMetaObject::Connection的新方式

QMetaObject::Connection m_connection;
//…
m_connection = QObject::connect(…);
//…
QObject::disconnect(m_connection);

這適用於所有場景下,包括lambda函數和偽函數。

更簡單的異步

隨着C++ 11,它可以保持代碼內聯

void doYourStuff(const QByteArray &page)
{
 QTcpSocket *socket = new QTcpSocket;
 socket->connectToHost("qt.io", 80);
 QObject::connect(socket, &QTcpSocket::connected, [socket, page] () {
 socket->write(QByteArray("GET " + page + ""));
 });
 QObject::connect(socket, &QTcpSocket::readyRead, [socket] () {
 qDebug()<< "GOT DATA "<< socket->readAll();
 });
 QObject::connect(socket, &QTcpSocket::disconnected, [socket] () {
 qDebug()<< "DISCONNECTED ";
 socket->deleteLater();
 });
 
 QObject::connect(socket, static_cast<void (QTcpSocket::*)(QAbstractSocket::SocketError)>
(&QAbstractSocket::error), [socket](QAbstractSocket::SocketError) {
 qDebug()<< "ERROR " << socket->errorString();
 socket->deleteLater();
 });
}

下面是一個不用重新進入事件循環的QDialog,保持代碼屬於:

void Doc::saveDocument() {
 QFileDialog *dlg = new QFileDialog();
 dlg->open();
 QObject::connect(dlg, &QDialog::finished, [dlg, this](int result) {
 if (result) {
 QFile file(dlg->selectedFiles().first());
 // …
 }
 dlg->deleteLater();
 });
 
}

使用QHttpServer的另外例子。

錯誤報告

用GCC測試的、
幸運的是,IDE能簡化函數的命名,比如Qt Creator。

忘記Q_OBJECT

#include <QtCore>
class Goo : public QObject {
 Goo() {
 connect(this, &Goo::someSignal, this, &QObject::deleteLater);
 }
signals:
 void someSignal();
};

qobject.h: In member function 'void QObject::qt_check_for_QOBJECT_macro(const T&&) const [with T = Goo]':
qobject.h:535:9: instantiated from 'static typename QtPrivate::QEnableIf<((int)
(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type QObject::connect(const typename
QtPrivate::FunctionPointer<Func>::Object*, Func1, const typename QtPrivate::FunctionPointer<Func2>::Object*,
Func2, Qt::ConnectionType) [with Func1 = void (Goo::*)(), Func2 = void (QObject::*)(), typename
QtPrivate::QEnableIf<((int)(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type = void*, typename 
QtPrivate::FunctionPointer<Func>::Object = Goo, typename QtPrivate::FunctionPointer<Func2>::Object = QObject]'
main.cc:4:68: instantiated from here
qobject.h:353:5: error: void value not ignored as it ought to be
make: '''* [main.o] Error 1

拼寫錯誤

#include <QtCore>
class Goo : public QObject {
Q_OBJECT
public:
 Goo() {
 connect(this, &Goo::someSignal, this, &Goo::someSlot1); //error
 connect(this, &Goo::someSignal, this, &Goo::someSlot2); //works
 }
signals:
 void someSignal(QString);
public:
 void someSlot1(int);
 void someSlot2(QVariant);
};

qobject.h: In static member function 'static typename QtPrivate::QEnableIf<((int)
(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type QObject::connect(const typename
QtPrivate::FunctionPointer<Func>::Object*, Func1, const typename QtPrivate::FunctionPointer<Func2>::Object*,
Func2, Qt::ConnectionType) [with Func1 = void (Goo::*)(QString), Func2 = void (Goo::*)(int), typename
QtPrivate::QEnableIf<((int)(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type = void*, typename 
QtPrivate::FunctionPointer<Func>::Object = Goo, typename QtPrivate::FunctionPointer<Func2>::Object = Goo]':
main.cc:6:62: instantiated from here
qobject.h:538:163: error: no type named 'IncompatibleSignalSlotArguments' in 'struct 
QtPrivate::CheckCompatibleArguments<QtPrivate::List<QString, void>, QtPrivate::List<int, void>, true>'
qobject.h: In static member function 'static void QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::call
(QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::Function, Obj*, void*) [with Args = QtPrivate::List<QString, void>,
Obj = Goo, Ret = void, Arg1 = int, QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::Function = void (Goo::*)(int)]':
qobject.h:501:13: instantiated from 'void QObject::QSlotObject<Func, Args>::call(QObject*, void**) [with Func =
void (Goo::*)(int), Args = QtPrivate::List<QString, void>, QObject = QObject]'
main.cc:14:2: instantiated from here
qobject.h:109:13: error: cannot conver

開放式問題

槽中的默認參數

如果你有類似下面的代碼:

class A : public QObject { Q_OBJECT
 public slots:
 void someSlot(int foo = 0);
};

舊的連接方式允許你連接這個槽到信號上,不用帶參數。 但是我不能從模板代碼中知道一個函數是否帶有默認參數。因此這個功能是被禁用的。 這里有個實現方法是,如果槽函數中參數數量多於信號函數中的參數數量時,退回到舊方式去連接。 不管怎樣,這是相當不一致的,因此舊語法不再執行類型類型檢查和類型轉換。 它已經從分支中移除,並被合並。

重載

如你在上面例子中看到的那樣,連接到QAbstractSocket::error,它不是真正完美的方式,因為error有一個重載。取得一個重載函數的地址需要隱式轉換。比如一個連接如下所示:

connect(mySpinBox, SIGNAL(valueChanged(int)), mySlider, SLOT(setValue(int));

它不能簡單的轉換到:

connect(mySpinBox, &QSpinBox::valueChanged, mySlider, &QSlider::setValue);

因為 QSpinBox有2個信號,名字都叫valueChanged()帶有不同的參數。 新方式要用下列代碼替代:

connect(mySpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), mySlider, &QSlider::setValue);

最好的方式是盡量不要重載信號和槽。
但是我們已經在過去的release版本中添加重載了,因為取得函數的地址不是我們支持的使用方式。 但是現在不破壞代碼兼用性已經是不可能的。

斷開連接

是否QMetaObject::Connection應該有一個disconnect()函數?
其他的難題是,如果我們使用新語法,在一些對象關閉時,不能自動斷開連接。一個方式是在斷開連接中添加對象的集合,或者一個新函數例如QMetaObject::Connection::require

auto c = connect(sender, &Sender::valueChanged, [=](const QString &newValue) {
 receiver->updateValue("senderValue", newValue);
 } , QList<QObject> { receiver } ); // solution 1
c.require(receiver); // solution 2

方案2是否有效? 沒有什么比得上QMetaObject::Connection::require()

回調

函數例如QHostInfo::lookupHost或QTimer::singleShot或QFileDialog::open 帶有一個QObject接收者和 char* 的slot。 這在新方式中是不能用的。 如果你想用c++方式的回調,應該使用 std::function (or tr1)。但我們不能在我們的API中,使用STL類型,因此一個qt函數應該被完成當復制一個std::function時。 無論如何,這是和QObject連接是不相關的。

譯自:https://wiki.qt.io/New_Signal_Slot_Syntax


免責聲明!

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



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