Подключите QML-сигнал к слоту lambda С++ 11 (Qt 5) - программирование
Подтвердить что ты не робот

Подключите QML-сигнал к слоту lambda С++ 11 (Qt 5)

Подключение QML-сигнала к регулярному слоту С++ очень просто:

// QML
Rectangle { signal foo(); }

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

Однако, что бы я ни старался, я не могу подключиться к слоту лямбда-функции С++ 11.

// 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++11
QObject::connect(some_qml_container, &QMLContainer::foo, [=]() { /* response */ }); // works!

Этот синтаксис не может работать для сигнала QML, поскольку подпись QMLContainer:: foo неизвестна во время компиляции (и объявление QMLContainer:: foo вручную поражает цель использования QML в первую очередь.)

Я пытаюсь сделать это? Если да, то какой правильный синтаксис для вызова QObject:: connect?

4b9b3361

Ответ 1

Lambdas и т.д. работают только с новым синтаксисом. Если вы не можете найти способ дать QML-сигнал в качестве указателя, я думаю, что это невозможно.

Если это так, у вас есть обходной путь: создайте подклассу QObject для маршрутизации сигнальных маршрутов, в которой есть только сигналы, по одному для каждого QML-сигнала, который вам нужен для маршрутизации. Затем подключите сигналы QML к соответствующим сигналам экземпляра этого фиктивного класса, используя старый синтаксис connect.

Теперь у вас есть сигналы С++, которые вы можете использовать с новым синтаксисом, и подключитесь к lambdas.

Класс может также иметь вспомогательный метод для автоматизации соединений из QML с сигналами класса, которые будут использовать механизмы отражения QMetaObject и подходящую схему именования сигналов, используя тот же принцип, что и используется QMetaObject:: connectSlotsByName. В качестве альтернативы вы можете просто жестко закодировать соединения сигнала QML-маршрутизатора, но все же скрывать их внутри метода класса маршрутизатора.

Непроверено...

Ответ 2

Вместо того, чтобы создавать лямбда-функции "на лету" для работы с разными сигналами, вы можете захотеть использовать 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, подключающийся к экземпляру top 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;
}

Ответ 3

Вы можете использовать помощника:

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 владеет вспомогательным объектом и очистит его при его уничтожении.