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