2013-03-25 285 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