2013-03-25 7 views
14

Collegamento di un segnale di QML ad un normale C++ slot è facile:Collegare QML segnale al C++ 11 slot di lambda (Qt 5)

// QML 
Rectangle { signal foo(); } 

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

Tuttavia, non importa quello che cerco, io non riesco a essere in grado per connettersi a uno slot di funzione lambda C++ 11.

// C++11 
QObject::connect(some_qml_container, SIGNAL(foo()), [=]() { /* response */ }); // fails... 
QObject::connect(some_qml_container, "foo()", [=]() { /* response */ }); // fails... 

Entrambi i tentativi falliscono con un errore di firma di funzione (senza QObject :: Connect sovraccarico può accettare questi parametri). Tuttavia, la documentazione Qt 5 implica che ciò dovrebbe essere possibile.

Sfortunatamente, Qt 5 esempi collegano sempre segnale di C++ in un C++ fessura lambda:

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

Questa sintassi non può funzionare per un segnale QML, come QMLContainer :: firma foo non è noto al momento della compilazione (e dichiarando che QMLContainer :: pippo a mano sconfigge lo scopo di usare QML in primo luogo.)

È quello che sto cercando di fare? In tal caso, qual è la sintassi corretta per la chiamata QObject :: connect?

risposta

6

Lambdas ecc funziona solo con una nuova sintassi. Se non riesci a trovare un modo per fornire il segnale QML come puntatore, allora penso che non sia direttamente possibile.

Se è così, si dispone di una soluzione: creare un manichino segnale di routing QObject sottoclasse, che ha solo segnali, uno per ogni QML segnale è necessario percorso. Quindi connettere i segnali QML ai segnali corrispondenti di un'istanza di questa classe dummy, usando la vecchia sintassi connect.

Ora si dispone di segnali C++ che è possibile utilizzare con la nuova sintassi e connettersi a lambda.

La classe potrebbe anche avere un metodo di supporto, per automatizzare le connessioni da QML ai segnali della classe, che utilizzerebbe i meccanismi di riflessione QMetaObject e uno schema di denominazione dei segnali appropriato, utilizzando lo stesso principio degli usi QMetaObject::connectSlotsByName. In alternativa è possibile semplicemente codificare le connessioni del segnale del router QML, ma nasconderlo comunque all'interno di un metodo della classe router.

testato ...

+0

Grazie per la risposta, questo mi dà una nuova direzione per cercare una risposta: è possibile ottenere un puntatore C++ su un segnale QML? Se è così, posso associare una funzione std :: al segnale e una lambda allo slot. Sfortunatamente, il mirroring di ogni segnale QML in un QObject in C++ è (discutibilmente) un design peggiore rispetto alla definizione degli slot in QObjects (cioè l'approccio old-school). Quello che vorrei fare è evitare di utilizzare completamente QObjects, sfruttando le nuove interfacce Qt 5 (che potrebbero o non potrebbero essere possibili). –

+0

Bene, il fatto che le connessioni segnale-segnale avvengano automaticamente potrebbe significare che è solo una riga aggiuntiva di codice, qualcosa come questa dopo aver dichiarato il visualizzatore QML: 'MyQMLSignalRouter qmlSignals (& myQmlView.rootObject());' e quindi usa 'qmlSignals' in new stile di collegare le chiamate. I segnali QML non esistono come funzioni C++, non possono (sono dinamici, C++ è statico), quindi ottenere un puntatore del metodo diretto su di essi non è nemmeno teoricamente possibile, a quanto ho capito. – hyde

+0

Il mio scetticismo per questo approccio consiste nello stretto accoppiamento tra i segnali QML e il codice C++ che introduce, così come il singolo approccio "super-classe" (una classe per dichiarare tutti i segnali, ovunque). Ha un cattivo odore! Hai perfettamente ragione che i segnali QML non sono disponibili per C++ staticamente. Tuttavia esiste una soluzione dinamica: QQuickItem :: metaObject() -> indexOfSignal ("foo()") restituisce correttamente l'indice di quel segnale. AFAICT, esiste anche l'impianto idraulico per ottenere un wrapper chiamabile, ma è nascosto all'interno dello spazio dei nomi QtPrivate. Bummer. –

2

Invece di creare funzioni lambda al volo a che fare con segnali diversi, si consiglia di considerare l'utilizzo di un QSignalMapper di intercettare i segnali e li invia ad uno slot per staticamente definito con un argomento dipende dalla fonte. Il comportamento della funzione dipenderebbe quindi interamente dalla fonte del segnale originale.

Il trade-off con QSignalMapper è che ottieni informazioni sulla sorgente del segnale, ma perdi gli argomenti originali. Se non puoi permetterti di perdere gli argomenti originali o se non conosci la fonte dei segnali (come nel caso dei segnali QDBusConnection::connect()), non ha senso usare uno QSignalMapper.

l'esempio di hyde richiederebbe un po 'più di lavoro, ma ti permetterebbe di implementare una versione migliore di QSignalMapper in cui puoi aggiungere informazioni sul segnale sorgente agli argomenti quando colleghi il segnale alla funzione di slot.

QSignalMapper riferimento classe: http://qt-project.org/doc/qt-5.0/qtcore/qsignalmapper.html
Esempio: http://eli.thegreenplace.net/2011/07/09/passing-extra-arguments-to-qt-slots/

Ecco un esempio increspatura un segnale attraverso un'istanza QSignalMapper collega ad una parte superiore ApplicationWindow esempio con una objectName di "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; 
} 
2

Puoi utilizzare un helper:

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

Poi:

LambdaHelper::connect(sender, SIGNAL(mySignal()), [] { ... }); 

Il sender possiede l'oggetto di supporto e sarà ripulirlo dalla sua distruzione.

+0

... e ora hai un memleak – Teimpz

+0

@Teimpz Niente affatto! –