New Signal Slot Syntax/Zh

From Qt Wiki
< New Signal Slot Syntax
Revision as of 16:52, 10 May 2021 by XQChen (talk | contribs) (Created page with "{{LangSwitch}} Category:Developing Qt::Qt Planning::Qt Public Roadmap Category:Developing Qt::Qt Planning Category:Developing Qt::Qt5 此页面用于描述新的...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

En Ar Bg De El Es Fa Fi Fr Hi Hu It Ja Kn Ko Ms Nl Pl Pt Ru Sq Th Tr Uk Zh

此页面用于描述新的信号和插槽语法。该功能现已随Qt 5一起发布。

注意:这是对旧的基于字符串的语法的补充,该语法仍然有效。

在Qt 5中进行连接

在Qt 5中有几种连接信号的方法。

旧语法

Qt 5继续支持基于旧的基于字符串的语法,用于连接QObject或从QObject继承的任何类(包括QWidget)中定义的信号和槽。

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

新增:连接到QObject成员

这是Qt 5连接两个QObject并传递非字符串对象的新方法:

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

优点

  • 编译时检查是否存在信号和插槽,类型,或者是否缺少Q_OBJECT。
  • 参数可以通过typedef定义,也可以使用不同的名称空间说明符,并且可以使用。
  • 如果存在隐式转换(例如,从QString到QVariant),则可以自动转换类型。
  • 可以连接到QObject的任何成员函数,而不仅仅是插槽。

缺点

  • 更复杂的语法?(您需要指定对象的类型)
  • 过载情况下的语法非常复杂吗?(见 下文
  • 不再支持插槽中的默认参数。

新增:连接到简单功能

新语法甚至可以连接到函数,而不仅仅是QObjects:

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

优点

  • 可以与std::bind一起使用::
connect(
    sender, &Sender::valueChanged,
    std::bind( &Receiver::updateValue, receiver, "senderValue", std::placeholders::_1 )
);
  • 可以与C++11 lambda表达式一起使用:
connect(
    sender, &Sender::valueChanged,
    [=]( const QString &newValue ) { receiver->updateValue( "senderValue", newValue ); }
);

缺点

  • 销毁“接收器”时不会自动断开连接,因为它是没有QObject的函子。但是,从5.2开始,有一个重载会添加一个“上下文对象”。当该对象被销毁时,连接断开(上下文也用于线程亲缘关系:将在用作上下文的对象的事件循环的线程中调用lambda)。

在Qt 5中断开连接

如您所料,在Qt 5中如何终止连接也有一些变化。

旧方法

您可以使用旧方法(使用SIGNAL,SLOT)断开连接,但前提是

  • 您使用旧方法进行连接,或者
  • 如果要使用通配符将所有插槽与给定信号断开连接

与函数指针对称

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

仅在通过函数指针与对称调用连接时才有效(或者也可以对通配符使用0)。特别是,不适用于静态函数,函子或lambda函数。

使用QMetaObject::Connection的新方法n

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,无需重新输入eventloop,并将代码保留在其所属位置:

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另一个示例 :http://pastebin.com/pfbTMqUm

错误报告

使用GCC测试。

幸运的是,像Qt Creator这样的IDE可以简化函数命名

类定义中缺少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 convert 'QtPrivate::RemoveRef<QString>::Type' to 'int' in argument passing
make: *** [main.o] Error 1

悬而未决的问题

插槽中的默认参数

如果您有这样的代码:

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

旧方法允许您将该插槽连接到没有参数的信号。但是我不知道模板代码中的函数是否具有默认参数。因此此功能被禁用。

如果插槽中的参数比信号中的参数多,则有一种方法可以回溯到旧方法。但是,这是非常不一致的,因为旧方法不执行类型检查或类型转换。它已从已合并的修补程序中删除。

重载

如您在上面的示例中可能看到的那样,由于error具有重载,因此连接到QAbstractSocket::error并不是很漂亮,并且获取重载函数的地址需要显式转换,例如,以前按如下方式进行的连接:


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

不能简单地转换为:

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

...因为QSpinBox有两个名为valueChanged()且具有不同参数的信号。相反,新代码需要为:

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

不幸的是,在此处使用显式强制转换会导致多种类型的错误流失到编译器中。添加临时变量赋值可保留以下编译时检查:

void (QSpinBox::* mySignal)(int) = &QSpinBox::valueChanged;
connect(
    mySpinBox, mySignal,
    mySlider, &QSlider::setValue
);

某些宏可能会有所帮助(使用C++11或typeof扩展名)。Qt 5.7中引入了基于模板的解决方案: qOverload

最好的做法可能是建议不要重载信号或槽...

…但是我们在过去的Qt的次要版本中一直添加重载,因为获取函数的地址不是我们支持的用例。但是现在在不破坏源兼容性的情况下这将是不可能的。

断开连接

QMetaObject::Connection是否应该具有disconnect()函数?

另一个问题是,如果我们使用采用闭包的语法,则闭包中的某些对象不会自动断开连接。可以在断开连接中添加对象列表,或者添加诸如QMetaObject::Connection::require之类的新功能。

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

// Solution 2 (needs a new definition of QMetaObject::Connection::require)
c.require( receiver );


回调

QHostInfo::lookupHost(直到Qt 5.9)或QTimer::singleShot(直到Qt 5.4)或QFileDialog::open之类的函数带有QObject接收器和char*槽。这不适用于新方法。如果要使用回调C++方式,则应使用std::function, 但是我们不能在ABI中使用STL类型,因此应执行QFunction来复制std::function。无论如何,这与QObject连接无关。