【问题标题】:Connect QML signal to C++11 lambda slot (Qt 5)将 QML 信号连接到 C++11 lambda 插槽 (Qt 5)
【发布时间】:2013-03-15 12:34:48
【问题描述】:

将 QML 信号连接到常规 C++ 插槽很容易:

// 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 重载可以接受这些参数)。但是,Qt 5 文档暗示这应该是可能的。

不幸的是,Qt 5 示例总是将 C++ 信号连接到 C++ lambda 槽:

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

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

我正在尝试做的事情可能吗?如果是这样,QObject::connect 调用的正确语法是什么?

【问题讨论】:

    标签: c++ qt c++11 signals-slots qt5


    【解决方案1】:

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

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

    现在您有了可以与新语法一起使用的 C++ 信号,并连接到 lambda。

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

    未经测试...

    【讨论】:

    • 感谢您的回答,这给了我寻找答案的新方向:是否有可能获得指向 QML 信号的 C++ 指针?如果是这样,我可以将 std::function 绑定到信号并将 lambda 绑定到插槽。不幸的是,将每个 QML 信号镜像到 C++ QObject 中(可以说)比仅定义 QObjects 中的插槽(即老式方法)更糟糕。我想做的是完全避免使用 QObjects,利用新的 Qt 5 接口(这可能会也可能不会)。
    • 好吧,让信号-信号连接自动发生可能意味着它只是额外的一行代码,就像声明 QML 查看器后的这一行:MyQMLSignalRouter qmlSignals(&myQmlView.rootObject());,然后以新样式使用 qmlSignals连接通话。 QML 信号不以 C++ 函数的形式存在,它们不能(它们是动态的,C++ 是静态的),因此据我了解,在理论上甚至不可能获得指向它们的直接方法指针。
    • 我对这种方法的怀疑在于 QML 信号和它引入的 C++ 代码之间的紧密耦合,以及单一的“超类”方法(一个类来声明所有信号,无处不在)。真难闻!您完全正确,QML 信号不能静态地用于 C++。但是可能存在动态解决方案: QQuickItem::metaObject()->indexOfSignal("foo()") 正确返回该信号的索引。 AFAICT,获取可调用包装器的管道也存在,但隐藏在 QtPrivate 命名空间内。无赖。
    • 好吧,你需要编写静态 C++ 代码来调用 connect 来连接 lambda。此时,使用自动连接,如果您是第一次连接该信号,您只需将该信号也添加到路由器类(在 .h 文件中添加一行)。如果在编译时不知道信号名称,那么您需要路由器类中的占位符信号和两个连接(从 QML 到占位符信号的动态旧式连接,从那个到 lambda 的静态新式连接)。 我同意这有点讨厌,但是 lambdas 有闭包的好处,如果这适用于你的代码,那么我会说去吧。
    • 经过深思熟虑,我最终实现了您的建议。它可以工作,并且让 QML 方面对 C++ 细节一无所知(更清晰,更可测试)。通过一些工作,可以创建一个多态的 SingalRouter 类以避免事先指定每个插槽。谢谢!
    【解决方案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 拥有辅助对象,并将在其销毁时对其进行清理。

    【讨论】:

    • ...现在你有一个内存泄漏
    • @Teimpz 一点也不!
    【解决方案3】:

    您可能需要考虑使用QSignalMapper 来拦截信号并将它们发送到静态定义的插槽,而不是动态创建 lambda 函数来处理不同的信号。函数的行为将完全取决于原始信号的来源。

    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 实例和objectName"app_window" 实例的示例:

    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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-08
      • 1970-01-01
      • 2016-10-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多