【问题标题】:Invoke slot of child of child of QQmlPropertyMap?调用 QQmlPropertyMap 子的子槽?
【发布时间】:2020-12-21 22:21:51
【问题描述】:

在将此标记为“无法在 QQmlPropertyMap 的子类中从 QML 调用 slot 或 Q_INVOKABLE”的副本之前,请阅读最终回复 (https://stackoverflow.com/a/18674363/447438)。

我正在设计一个继承自 QQmlPropertyMap 的类(称为 MyBaseClass)。它为我的系统实现了一些额外的行为,然后被其他从MyBaseClass(例如ChildClass)初始化的类使用。

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

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";
    };
};

ma​​in.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();
    }
}

ma​​in.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();
}

【问题讨论】:

  • 您提供的链接指向错误报告的链接,该报告链接回documentation,其中包括以下位:“注意:从 QQmlPropertyMap 派生类时,请使用受保护的双参数构造函数,以确保该类已正确注册到 Qt 元对象系统。"
  • JarMan,基类 (MyBaseClass) 已经在这样做了。尝试将 QQmlPropertyMap 的继承添加到 ChildClass 会导致错误。 “/home/jwerner/Projects/InheritanceTest-simple/ChildClass.h:8: 警告:由于歧义,直接基 'QQmlPropertyMap' 无法访问:Class ChildClass -> class MyBaseClass -> class QQmlPropertyMap class ChildClass -> class QQmlPropertyMap”跨度>
  • @jwernerny 你不能从 QObject 乘法派生,所以从 QQmlPropertyMap 重新继承当然是行不通的,我确定这不是暗示。
  • “我正在设计一个继承自 QQmlPropertyMap 的类(称为 MyBaseClass)”——这就是问题的根源。 “Qt 方式”需要一个明确的答案,说明为什么这是必要的以及您想要实现的目标。也许这是一个不必要的并发症。如果属性不是动态创建的,QQmlPropertyMap 则根本不需要,在这种情况下,Qt 6 的可绑定属性会这样做。
  • 我想做的是创建一种可重用的方式将数据从 C++ 接口层传递到 QML 显示层。我们交互的每个事物都有它自己的数据,并且可能有我们需要从 UI 触发的事物。我们还不知道事物的所有数据(而且它一直在变化),因此必须具备创建动态数据的能力。

标签: c++ qml qt5 qt-signals


【解决方案1】:

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

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

因此:

#include <QQmlPropertyMap>

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

构造函数应该受到保护,因为MyBaseClass 不能单独使用,我想。如果您愿意,您可以随时添加单参数公共构造函数:

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 声明为最终类,

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

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

【讨论】:

  • 我一直在考虑使用模板。我会尝试一下,并针对我的单元测试运行它以获取真实代码。我认为倾向于首先考虑继承,然后是模板。
  • 我已经在我们的真实应用程序中进行了测试,它可以工作。谢谢!
【解决方案2】:

我对此进行了测试,它确实有效,但我不能说我推荐它作为解决方案。如果它必须知道它的派生类型,它有点否定了拥有一个基类的目的。

// 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)
    {}

};

【讨论】:

  • 重点是让它成为一个 template 构造函数。不需要转发声明任何东西,也不需要提前知道子类的类型。 QQmlPropertyMap 已经做到了,你基本上可以复制粘贴这个想法(好吧,你必须这样做,只有一种方法可以做到)。
  • 是的,你当然是对的。我发布这个是因为它有效,但知道这是错误的代码。很高兴有更好的方法。
猜你喜欢
  • 2013-02-07
  • 1970-01-01
  • 2018-09-16
  • 1970-01-01
  • 2018-02-21
  • 1970-01-01
  • 1970-01-01
  • 2020-01-06
  • 1970-01-01
相关资源
最近更新 更多