2013-03-25 56 views
14

一个QML信号连接到一个普通的C++插槽很简单:连接QML信号C++ 11拉姆达插槽(QT 5)

// QML 
Rectangle { signal foo(); } 

// C++ old-style 
QObject::connect(some_qml_container, SIGNAL(foo()), some_qobject, SLOT(fooSlot()); // works! 

但是,不管我怎么努力,我似乎​​无法能够连接到C++ 11 lambda功能插槽。

// C++11 
QObject::connect(some_qml_container, SIGNAL(foo()), [=]() { /* response */ }); // fails... 
QObject::connect(some_qml_container, "foo()", [=]() { /* response */ }); // fails... 

这两次尝试都失败并出现函数签名错误(没有QObject :: connect overload可以接受这些参数)。但是,Qt 5文档意味着这应该是可能的。

不幸的是,Qt的5例的C++信号总是连接到C++拉姆达槽:

// C++11 
QObject::connect(some_qml_container, &QMLContainer::foo, [=]() { /* response */ }); // works! 

此语法不能为QML信号工作,因为QMLContainer :: foo的签名不是在编译时已知(并且手动声明QMLContainer :: foo会首先破坏使用QML的目的。)

正在尝试做什么?如果是这样,QObject :: connect调用的正确语法是什么?

回答

6

Lambdas等只适用于新的语法。如果您找不到一种将QML信号作为指针的方法,那么我认为这不是直接可能的。

如果是这样,你有一个解决方法:创建一个虚拟信号路由QObject子类,它只有信号,每个QML信号需要路由一个。然后使用旧连接语法将QML信号连接到此虚拟类实例的相应信号。

现在,您可以使用新语法的C++信号,并连接到lambda表达式。

该类还可以有一个辅助方法,用于自动执行从QML到类信号的连接,该类将使用QMetaObject反射机制和合适的信号命名方案,使用与QMetaObject::connectSlotsByName相同的原理。或者,您可以对QML路由器信号连接进行硬编码,但仍将其隐藏在路由器类的方法中。

未经测试...

+0

感谢您的回答,这给了我一个新的方向来寻找答案:是否有可能获得C++指针到QML信号?如果是这样,我可以将一个std ::函数绑定到该信号,并将一个lambda绑定到该插槽。 不幸的是,将每个QML信号镜像到一个C++ QObject中(可以说)是一种比定义QObject中的插槽更糟糕的设计(即旧式的方法)。我想要做的就是完全避免使用QObject,并利用新的Qt 5接口(可能或不可能)。 – 2013-03-26 08:49:46

+0

那么,自动发生信号 - 信号连接可能意味着它只是一个额外的代码行,类似于在声明QML查看器之后的这一行:'MyQMLSignalRouter qmlSignals(&myQmlView.rootObject());'然后在新中使用'qmlSignals'式连接调用。 QML信号并不作为C++函数存在,它们不能(它们是动态的,C++是静态的),所以直接指向它们的方法指针在理论上是不可能的,据我所知。 – hyde 2013-03-26 09:04:18

+0

我对此方法的怀疑在于它引入的QML信号与C++代码之间的紧密耦合,以及单一的“超类”方法(一个类声明所有信号,无处不在)。真难闻! 你是完全正确的,QML信号静态不可用于C++。然而,动态解决方案可能存在: QQuickItem :: metaObject() - > indexOfSignal(“foo()”) 正确返回该信号的索引。 AFAICT,用于获取可调用封装的管道也存在,但隐藏在QtPrivate命名空间中。游民。 – 2013-03-26 10:07:17

2

而是在飞行应对不同的信号,从而造成lambda函数,你可能要考虑使用QSignalMapper拦截信号,并将其发送到静态定义插槽与争论取决于来源。函数的行为将完全取决于原始信号的来源。

QSignalMapper的权衡取舍是您获得有关信号源的信息,但您失去了原始参数。如果你不能承受失去原有的参数,或者如果你不知道信号源(如与QDBusConnection::connect()信号的情况下),那么它并没有真正意义的使用QSignalMapper.

hyde的例子需要多一点的工作,但是可以让你实现更好的版本QSignalMapper,你可以在将信号连接到插槽功能时将源信号的信息添加到参数中。

QSignalMapper类参考:http://qt-project.org/doc/qt-5.0/qtcore/qsignalmapper.html
实施例:http://eli.thegreenplace.net/2011/07/09/passing-extra-arguments-to-qt-slots/

这里是一个例子荡漾通过QSignalMapper实例连接到顶部ApplicationWindow实例与"app_window"objectName一个信号:

for (auto app_window: engine.rootObjects()) { 
    if ("app_window" != app_window->objectName()) { 
    continue; 
    } 
    auto signal_mapper = new QSignalMapper(&app); 

    QObject::connect(
    app_window, 
    SIGNAL(pressureTesterSetup()), 
    signal_mapper, 
    SLOT(map())); 

    signal_mapper->setMapping(app_window, -1); 

    QObject::connect(
    signal_mapper, 
    // for next arg casting incantation, see http://stackoverflow.com/questions/28465862 
    static_cast<void (QSignalMapper::*)(int)>(&QSignalMapper::mapped), 
    [](int /*ignored in this case*/) { 
     FooSingleton::Inst().Bar(); 
    }); 
    break; 
} 
2

你可以使用帮手:

class LambdaHelper : public QObject { 
    Q_OBJECT 
    std::function<void()> m_fun; 
public: 
    LambdaHelper(std::function<void()> && fun, QObject * parent = {}) : 
    QObject(parent), 
    m_fun(std::move(fun)) {} 
    Q_SLOT void call() { m_fun(); } 
    static QMetaObject::Connection connect(
    QObject * sender, const char * signal, std::function<void()> && fun) 
    { 
    if (!sender) return {}; 
    return connect(sender, signal, 
        new LambdaHelper(std::move(fun), sender), SLOT(call())); 
    } 
}; 

然后:

LambdaHelper::connect(sender, SIGNAL(mySignal()), [] { ... }); 

sender拥有辅助对象,并将清理后其销毁。

+0

...现在你有一个memleak – Teimpz 2017-10-04 16:12:44

+0

@Teimpz一点都不! – 2017-10-18 16:30:51