调用QQmlPropertyMap child child slot?

Invoke slot of child of child of QQmlPropertyMap?

在将此标记为“无法在 QQmlPropertyMap 的子 class 中从 QML 调用插槽或 Q_INVOKABLE”的副本之前,请阅读最终回复 ()。

我正在设计一个从 QQmlPropertyMap 继承的 class(称之为 MyBaseClass)。它为我的系统实现了一些额外的行为,这些行为随后被从 MyBaseClass 继承的其他 classes 使用(例如 ChildClass)。

我希望从 QML (QtQuick) 调用 ChildClass 的槽 (mySlot())。当我尝试时,我在应用程序输出中遇到以下问题(提到 MyBaseClass),即使我只使用 ChildClass.

qrc:/main.qml:17: TypeError: Property 'mySlot' of object MyBaseClass(0x55f88ef86b50) is not a function
qrc:/main.qml:24: TypeError: Property 'mySlot' of object MyBaseClass(0x55f88ef86b50) is not a function

我也尝试过从 QML 发送信号,但我在尝试绑定到 QML 发出的信号时遇到了类似的问题。 (我没有添加那个例子。)

对于那些建议我将 QQmlPropertyMap 继承到 ChildClass 中的人(除了 MyBaseClass 之外,它已经通过受保护的方法对其进行了适当的继承和初始化),结果并不理想:

/home/jwerner/Projects/InheritanceTest-simple/ChildClass.h:8: warning: direct base 'QQmlPropertyMap' is inaccessible due to ambiguity:
    class ChildClass -> class MyBaseClass -> class QQmlPropertyMap
    class ChildClass -> class QQmlPropertyMap

我的“直觉”说问题是我没有按照“Qt 方式”做事,但我不知道它是什么。

这里是演示问题的最小示例代码。 (我省略了 .pro 文件,因为制作起来很简单。)

MyBaseClass.h

#include <QQmlPropertyMap>

class MyBaseClass : public QQmlPropertyMap
{
    Q_OBJECT
public:
    explicit MyBaseClass(QObject *parent = nullptr)
        : QQmlPropertyMap(this, parent)
    {}

};

ChildClass.h

#include "MyBaseClass.h"
#include <QObject>
#include <QDebug>

class ChildClass : public MyBaseClass
{
    Q_OBJECT
public:
    ChildClass(QObject *parent = nullptr)
        : MyBaseClass(parent) {}

public slots:
    void mySlot() {
        qDebug() << "This is mySlot";
    };
};

main.qml

import QtQuick 2.15
import QtQuick.Window 2.15


Window {
    id: theMainWindow
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    property QtObject   cData: childData;

    onCDataChanged: {
        childData.mySlot();
    }

    Timer { // wait a bit before calling
        repeat: false
        interval: 2000
        running: true
        onTriggered: childData.mySlot();
    }
}

main.cpp

int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    auto childData = new ChildClass();

    engine.rootContext()->setContextProperties({
        QQmlContext::PropertyPair{"childData",  QVariant::fromValue(childData)},
    });
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
        &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

观察到 2-argument QQmlPropertyMap 构造函数是一个 template 构造函数,它需要 [most] derived class 的类型(文档可能更清楚一点,但这是使其与自动注册一起工作的唯一方法)。它看到的唯一类型是 MyBaseClass.

您需要将派生的类型 class 传递给该构造函数,例如通过制作一个受保护的 MyBaseClass 构造函数 templated 并赋予它与 QQmlPropertyMap 相同的签名,然后从 MyChildClass 调用它。使 MyBaseClass 成为 CRTP 模板 class 的替代方案仅在 class 不添加任何属性、信号或插槽时才有效。

因此:

#include <QQmlPropertyMap>

class MyBaseClass : public QQmlPropertyMap
{
    Q_OBJECT
protected:
    template <typename Derived>
    explicit MyBaseClass(Derived *derived, QObject *parent = nullptr)
        : QQmlPropertyMap(derived, parent)
    {}
};

构造函数应该受到保护,因为我认为 MyBaseClass 不能单独使用。如果你愿意,你总是可以添加一个单参数 public 构造函数:

public:
    MyBaseClass(QObject *parent = nullptr) : QQmlPropertyMap(this, parent) {}

然后:

#include "MyBaseClass.h"
#include <QObject>
#include <QDebug>

class ChildClass /* final, or see below */ : public MyBaseClass
{
    Q_OBJECT
public:
    ChildClass(QObject *parent = nullptr)
        : MyBaseClass(this, parent) {}   // notice the additional argument

protected:
    // if this class is not final, then add:
    template <typename Derived>
    ChildClass(Derived *derived, QObject *parent = nullptr)
         : MyBaseClass(derived, parent) {}

public slots:
    void mySlot() { qDebug() << "This is mySlot"; };
};

请注意,必须存在以下 之一:

  1. class ChildClass 声明为最终 class、

  2. 模板化的受保护的 2 参数构造函数。

两者都没有或两者都没有是设计错误。

我测试了这个并且它有效,但我不能说我推荐这个作为解决方案。如果它必须知道其派生类型,那么它有点否定了拥有基数 class 的目的。

// Forward-declare the derived type so it can be passed to the constructor
class ChildClass;

class MyBaseClass : public QQmlPropertyMap
{
    Q_OBJECT
public:
    explicit MyBaseClass(ChildClass *derivedObj, QObject *parent = nullptr)
        : QQmlPropertyMap(derivedObj, parent)
    {}

};