С++ invokeMethod не может получить доступ к методу QML

У меня есть приложение, написанное на C++ (логика) и QML (пользовательский интерфейс). В части С++ у меня есть коллекция объекта QML (вид системы событий)

Это упрощенный код объекта С++:

class Config : public QObject
{
Q_OBJECT
    Q_ENUMS(DataEvent)
public:

    enum DataEvent {
        DataEventUndefined = 0,
        DataEventDateChanged
    };

    ~Config();
    Q_INVOKABLE void registerToEvent (DataEvent event, QQuickItem *item)
    {
        p_dataListeners.insert(event,item);
    }

private:
    QMap<DataEvent,QQuickItem *> p_dataListeners;
}

в объекте QML я вызываю функцию С++, и она работает как шарм. Это часть кода QML:

Item {
    id: myQMLObject
    function init() {
        Config.registerToEvent(Config.DataEventDateChanged,myQMLObject);
    }
    function receiveEvent(eventType) {
    ...
    }
}

Хорошо, теперь я хочу вызвать функцию QML одного из сохраненных объектов QML:

    event = Config::DataEventDateChanged;
    QMapIterator<DataEvent,QQuickItem *> i(p_dataListeners);
    while (i.hasNext()) {
        i.next();
        if(event == i.key()) {
            QQuickItem *item = i.value();
            QMetaObject::invokeMethod(item, "receiveEvent",
                                      QGenericReturnArgument(),
                                      Q_ARG(Config::DataEvent, event));
        }
    }

Но я получаю эту ошибку: QMetaObject::invokeMethod: No such method MyQMLObject_QMLTYPE_44::receiveEvent(Config::DataEvent)

Что я делаю неправильно?


person folibis    schedule 11.06.2014    source источник


Ответы (1)


Вы должны использовать QVariant в качестве типа аргумента:

#include <QApplication>
#include <QtQuick>

class Thing : public QObject
{
    Q_OBJECT
    Q_ENUMS(DataEvent)
public:
    enum DataEvent {
        DataEventUndefined = 0,
        DataEventDateChanged
    };

    Thing() {}

public slots:
    void registerToEvent(QQuickItem *stuff) {
        DataEvent event = Thing::DataEventDateChanged;
        QMetaObject::invokeMethod(stuff,
            "receiveEvent",
            QGenericReturnArgument(),
//            Q_ARG(Thing::DataEvent, event));
            Q_ARG(QVariant, event));
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QQmlApplicationEngine engine;
    Thing thing;
    engine.rootContext()->setContextProperty("thing", &thing);
    engine.load(QUrl(QStringLiteral("qrc:///main.qml")));

    return app.exec();
}

#include "main.moc"

main.qml:

import QtQuick 2.2
import QtQuick.Window 2.0

Window {
    visible: true
    width: 300
    height: 250

    Item {
        id: item

        Component.onCompleted: {
            thing.registerToEvent(item);
        }

        function receiveEvent(arg) {
            print(arg);
        }
    }
}

Это связано с тем, что тип аргумента для функций JavaScript в QML — QVariant. Убедиться в этом можно небольшим дополнением к qmetaobject.cpp:

diff --git a/src/corelib/kernel/qmetaobject.cpp b/src/corelib/kernel/qmetaobject.cpp
index accefb1..e39539c 100644
--- a/src/corelib/kernel/qmetaobject.cpp
+++ b/src/corelib/kernel/qmetaobject.cpp
@@ -1455,6 +1455,10 @@ bool QMetaObject::invokeMethod(QObject *obj,
     }

     if (idx < 0 || idx >= meta->methodCount()) {
+        for (int i = 0; i < meta->methodCount(); ++i) {
+            QMetaMethod method = meta->method(i);
+            qDebug() << method.methodSignature();
+        }
         qWarning("QMetaObject::invokeMethod: No such method %s::%s",
                  meta->className(), sig.constData());
         return false;

Когда вызов метода терпит неудачу, мы перебираем каждый метод объекта, известный moc. В приведенном выше примере это выводит:

"destroyed(QObject*)"
"destroyed()"
"objectNameChanged(QString)"
"deleteLater()"
"_q_reregisterTimers(void*)"
"childrenRectChanged(QRectF)"
"baselineOffsetChanged(double)"
"stateChanged(QString)"
"focusChanged(bool)"
"activeFocusChanged(bool)"
"activeFocusOnTabChanged(bool)"
"parentChanged(QQuickItem*)"
"transformOriginChanged(TransformOrigin)"
"smoothChanged(bool)"
"antialiasingChanged(bool)"
"clipChanged(bool)"
"windowChanged(QQuickWindow*)"
"childrenChanged()"
"opacityChanged()"
"enabledChanged()"
"visibleChanged()"
"visibleChildrenChanged()"
"rotationChanged()"
"scaleChanged()"
"xChanged()"
"yChanged()"
"widthChanged()"
"heightChanged()"
"zChanged()"
"implicitWidthChanged()"
"implicitHeightChanged()"
"update()"
"_q_resourceObjectDeleted(QObject*)"
"grabToImage(QJSValue,QSize)"
"grabToImage(QJSValue)"
"contains(QPointF)"
"mapFromItem(QQmlV4Function*)"
"mapToItem(QQmlV4Function*)"
"forceActiveFocus()"
"forceActiveFocus(Qt::FocusReason)"
"nextItemInFocusChain(bool)"
"nextItemInFocusChain()"
"childAt(double,double)"
"receiveEvent(QVariant)"

Обратите внимание на последний пункт "receiveEvent(QVariant)".

person Mitch    schedule 11.06.2014
comment
Хорошо, спасибо @Mitch! Попробую, когда доберусь до рабочего компа. Итак, вы говорите, что невозможно использовать какой-либо зарегистрированный тип, а только приводить его к QVariant в качестве параметра функции? - person folibis; 11.06.2014
comment
Это могло быть возможно; как я уже сказал, я не уверен. - person Mitch; 11.06.2014
comment
Я обновил свой ответ, объяснив, почему QVariant необходим. - person Mitch; 11.06.2014
comment
Спасибо @Mitch, еще раз! Действительно хорошее исследование проблемы. - person folibis; 13.06.2014