将QML信号连接到c++ 11 lambda插槽(Qt 5)



将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调用的正确语法是什么?

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

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

现在你有c++信号,你可以使用新的语法,并连接到lambdas。

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

未经测试…

您可以使用帮助器:

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拥有helper对象,并将在其销毁时清除它。

与其动态地创建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实例连接到objectName"app_window"的顶级ApplicationWindow实例:

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

最新更新