如何将 QObject 指针的属性公开给 QML

Posted

技术标签:

【中文标题】如何将 QObject 指针的属性公开给 QML【英文标题】:How to expose property of QObject pointer to QML 【发布时间】:2015-07-16 05:43:15 【问题描述】:

我正在对我的班级进行非常简短(和部分)的描述,以显示我的问题。基本上我已经设置了两个属性。

class Fruit : public QObject

Q_OBJECT
  ....
public:
    Q_PROPERTY( int price READ getPrice NOTIFY priceChanged)

    Q_PROPERTY(Fruit * fruit READ fruit WRITE setFruit NOTIFY fruitChanged)

在我的 QML 中,如果我访问 price 属性,它运行良好且良好。但是,如果我访问显然返回Fruitfruit 属性,然后尝试使用它的price 属性,那将不起作用。这不应该以这种方式工作吗?

Text 
    id: myText
    anchors.centerIn: parent
    text: basket.price // shows correctly
    //text: basket.fruit.price // doesn't show

第二个返回Fruit,它也是一个属性,它有price 属性,但它似乎没有访问该属性?这应该工作吗?

更新

我包括我的源代码。我用HardwareComponent 创建了一个新的演示,这样更有意义。我试图根据收到的答案使其工作,但没有运气。

HardwareComponent 类是ComputerCPU 的基类。

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

class HardwareComponent : public QObject

   Q_OBJECT
public:
   HardwareComponent() : m_price(0) 
   virtual int price() = 0;
   virtual Q_INVOKABLE void add(HardwareComponent * item)  m_CPU = item; 
   HardwareComponent * getCPU() const  return m_CPU; 

   Q_SLOT virtual void setPrice(int arg)
   
      if (m_price == arg) return;
      m_price = arg;
      emit priceChanged(arg);
   
   Q_SIGNAL void priceChanged(int arg);

protected:
   Q_PROPERTY(HardwareComponent * cpu READ getCPU);
   Q_PROPERTY(int price READ price WRITE setPrice NOTIFY priceChanged)

   HardwareComponent * m_CPU;
   int m_price;
;

class Computer : public HardwareComponent

   Q_OBJECT
public:
   Computer()  m_price = 500; 

   int price()  return m_price; 
   void setprice(int arg)  m_price = arg; 
;

class CPU : public HardwareComponent

   Q_OBJECT
public:
   CPU()  m_price = 100; 

   int price()  return m_price; 
   void setprice(int arg)  m_price = arg; 
;

int main(int argc, char *argv[])

   HardwareComponent * computer = new Computer;
   CPU * cpu = new CPU;
   computer->add( cpu );

   QGuiApplication app(argc, argv);
   QQmlApplicationEngine engine;

   engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
   engine.rootContext()->setContextProperty("computer", computer); 

   return app.exec();


#include "main.moc"

main.qml

import QtQuick 2.3
import QtQuick.Window 2.2

Window 
    visible: true
    width: 360
    height: 360

    Text 
        anchors.centerIn: parent
        text: computer.price // works
        //text: computer.cpu.price // doesn't work, doesn't show any value
    

这是我所有项目文件的完整源代码。

当我运行它时,我得到这个输出:

启动 C:\Users\User\Documents\My Qt Projects\build-hardware-Desktop_Qt_5_4_0_MSVC2010_OpenGL_32bit-Debug\debug\hardware.exe ... QML 调试已启用。仅在安全的环境中使用。 qrc:/MainForm.ui.qml:20: ReferenceError: computer is not defined

即使它在第 20 行 (computer.price) 行发出警告,它仍然有效并显示计算机的价格 (=500)。如果更改它computer.cpu.price,则会报告相同的警告,但价格不再显示 - 它似乎不起作用。

问题是由于价格是一个虚拟属性,它可以工作!但是如果我在计算机组件内的另一个硬件组件上使用此属性,它就不起作用! Mido 发布的代码/答案让我希望有一个解决方案,它看起来非常接近!我希望我能完成这项工作。

【问题讨论】:

“它不起作用”是什么意思?您收到哪个错误? 对不起,像这样的问题很糟糕。正如@skypjack 已经提到的,“它不起作用”是一个典型的、懒惰的短语,总是会被否决。 qCring 提出了一个不可避免的问题,即“您的其他代码在哪里我们无法回答这个问题”。你在这里已经四年了……我只是不明白。 这不是真的。如果您有一个最小的示例(几乎总是可以创建),那么 main.cpp 会非常小——它们通常用于 Qt Quick 应用程序。 我看过代码,你有3个错误:1.virtual Q_INVOKABLE void add(HardwareComponent * item);应该是Q_INVOKABLE virtual void add(HardwareComponent * item); 2.Q_PROPERTY( HardwareComponent * cpu READ getCPU );去掉;在行尾。 3. engine.rootContext()-&gt;setContextProperty("computer", computer); 应该放在engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 之前,这将解决您的警告问题。对于根本问题,您应该阅读如何在 qt doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html 中进行分组属性 根据答案修复问题中的代码是非常糟糕的形式,因为它使问题似乎不再有意义。我已恢复您的编辑。 【参考方案1】:

只有几处错误,特别是与 QML 相关:

    必须在引擎加载 qml 文件之前设置上下文属性。

    您尚未注册HardwareComponent 类型。请参阅Defining QML Types from C++ 了解更多信息。

除了 QML 方面,您似乎希望硬件是具有树结构的组合。由于QObject 已经是一个组合,您可以利用它来发挥自己的优势。硬件项目树可以是对象树。 QObject 会在添加或删除子代时通知父母:因此在添加和删除子代时很容易保持当前价格。

每个组件都有一个unitPrice:这是单独的组件的价格。 price 是组件的单价及其子项的价格的总和。

您可以缓存它并仅在单价变化或子价格变化时更新它,而不是遍历整个树来获取总价格。类似地,当前 CPU 可以被缓存,您可以在任何给定时间强制使用单个 CPU,等等。该示例是功能性的,但只是一个最小的草图。

我还展示了如何更改计算机中的 CPU 类型。价格变化和 CPU 变化都有通知。当 cpu 属性发生变化以及任何 price 属性发生变化时,QML UI 都会做出适当的反应。

main.cpp

#include <QGuiApplication>
#include <QtQml>

class HardwareComponent : public QObject 
   Q_OBJECT
   Q_PROPERTY(QString category MEMBER m_category READ category CONSTANT)
   Q_PROPERTY(HardwareComponent * cpu READ cpu WRITE setCpu NOTIFY cpuChanged)
   Q_PROPERTY(int price READ price NOTIFY priceChanged)
   Q_PROPERTY(int unitPrice MEMBER m_unitPrice READ unitPrice WRITE setUnitPrice NOTIFY unitPriceChanged)
   QString m_category;
   int m_unitPrice;
   bool event(QEvent * ev) Q_DECL_OVERRIDE 
      if (ev->type() != QEvent::ChildAdded && ev->type() != QEvent::ChildRemoved)
         return QObject::event(ev);
      auto childEvent = static_cast<QChildEvent*>(ev);
      auto child = qobject_cast<HardwareComponent*>(childEvent->child());
      if (! child) return QObject::event(ev);
      if (childEvent->added())
         connect(child, &HardwareComponent::priceChanged,
                 this, &HardwareComponent::priceChanged, Qt::UniqueConnection);
      else
         disconnect(child, &HardwareComponent::priceChanged,
                    this, &HardwareComponent::priceChanged);
      emit priceChanged(price());
      if (child->category() == "CPU") emit cpuChanged(cpu());
      return QObject::event(ev);
   
public:
   HardwareComponent(int price, QString category = QString(), QObject * parent = 0) :
      QObject(parent), m_category(category), m_unitPrice(price) 
   HardwareComponent * cpu() const 
      for (auto child : findChildren<HardwareComponent*>())
         if (child->category() == "CPU") return child;
      return 0;
   
   Q_INVOKABLE void setCpu(HardwareComponent * newCpu) 
      Q_ASSERT(!newCpu || newCpu->category() == "CPU");
      auto oldCpu = cpu();
      if (oldCpu == newCpu) return;
      if (oldCpu) oldCpu->setParent(0);
      if (newCpu) newCpu->setParent(this);
      emit cpuChanged(newCpu);
   
   Q_SIGNAL void cpuChanged(HardwareComponent *);
   virtual int price() const 
      int total = unitPrice();
      for (auto child : findChildren<HardwareComponent*>(QString(), Qt::FindDirectChildrenOnly))
         total += child->price();
      return total;
   
   Q_SIGNAL void priceChanged(int);
   int unitPrice() const  return m_unitPrice; 
   void setUnitPrice(int unitPrice) 
      if (m_unitPrice == unitPrice) return;
      m_unitPrice = unitPrice;
      emit unitPriceChanged(m_unitPrice);
      emit priceChanged(this->price());
   
   Q_SIGNAL void unitPriceChanged(int);
   QString category() const  return m_category; 
;

struct Computer : public HardwareComponent 
   Computer() : HardwareComponent(400) 
;

class FluctuatingPriceComponent : public HardwareComponent 
   QTimer m_timer;
   int m_basePrice;
public:
   FluctuatingPriceComponent(int basePrice, const QString & category = QString(), QObject * parent = 0) :
      HardwareComponent(basePrice, category, parent),
      m_basePrice(basePrice) 
      m_timer.start(250);
      connect(&m_timer, &QTimer::timeout, [this]
         setUnitPrice(m_basePrice + qrand()*20.0/RAND_MAX - 10);
      );
   
;

int main(int argc, char *argv[])

   QGuiApplication app(argc, argv);
   QQmlApplicationEngine engine;
   Computer computer;
   HardwareComponent memoryBay(40, "Memory Bay", &computer);
   HardwareComponent memoryStick(60, "Memory Stick", &memoryBay);
   FluctuatingPriceComponent cpu1(100, "CPU", &computer);
   HardwareComponent cpu2(200, "CPU");

   qmlRegisterUncreatableType<HardwareComponent>("bar.foo", 1, 0, "HardwareComponent", "");
   engine.rootContext()->setContextProperty("computer", &computer);
   engine.rootContext()->setContextProperty("cpu1", &cpu1);
   engine.rootContext()->setContextProperty("cpu2", &cpu2);
   engine.load(QUrl("qrc:/main.qml"));
   return app.exec();


#include "main.moc"

main.qml

import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Window 2.0

Window 
    visible: true
    Column 
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
        Text 
            text: "Computer price: " + computer.price
        
        Text 
            text: "CPU price: " + (computer.cpu ? computer.cpu.price : "N/A")
        
        Button 
            text: "Use CPU 1";
            onClicked:  computer.setCpu(cpu1) 
        
        Button 
            text: "Use CPU 2";
            onClicked:  computer.setCpu(cpu2) 
        
        Button 
            text: "Use no CPU";
            onClicked:  computer.setCpu(undefined) 
        
    

【讨论】:

太棒了!我从您的代码中提取了qmlRegisterUncreatableType 并将其放入我的代码中,然后 whalla.. 它就可以工作了!显然这就是我所需要的。谢谢! 好吧,基类中的price() 实现是因为我正在实现复合设计模式,我将再次重新审视它以确保我正确对齐,但每个HardwareComponent确实有价格信息那么为什么不在基类中定义价格呢? 我同意,在这个演示中,我想让属性的访问工作,所以我不关心方法的实现,但我认为setPrice() 不应该在基类中做任何事情,并且会更好。我认为你的建议听起来不错。 @zadane 我已经更新了答案以展示如何利用 QObject 是一个复合类这一事实。 谢谢,很有帮助。我仍然需要吸收event 功能,但更新后的答案更好。【参考方案2】:

我在您的示例中创建了一个 Fruit 类,它运行良好。我创建了一个Fruit 的新实例,我可以获得price 值。这是我的代码:

水果.h

#ifndef FRUIT_H
#define FRUIT_H

#include <QObject>
#include <QQuickView>

class Fruit : public QObject

    Q_OBJECT
    Q_PROPERTY( int price READ getPrice WRITE setPrice  NOTIFY priceChanged)
    Q_PROPERTY(Fruit * fruit READ fruit WRITE setFruit NOTIFY fruitChanged)

public:
    Fruit();
    ~Fruit();
    Fruit *fruit();
    void setFruit(Fruit * value);
    int getPrice();
    void setPrice(int value);

signals:
    void fruitChanged();
    void priceChanged(int price);

private:
    int _price;
    Fruit *_fruit;
;

#endif // FRUIT_H

水果.cpp

#include "fruit.h"

Fruit::Fruit() :
    _price(1),_fruit(this)



Fruit::~Fruit()

    if(_fruit)
    
        delete _fruit;
        _fruit = NULL;
    


Fruit *Fruit::fruit()

    //Uncomment this line to test the set
    //_fruit = new Fruit;
    return _fruit;


void Fruit::setFruit(Fruit *value)

    _fruit = value;
    emit fruitChanged();


int Fruit::getPrice()

    return _price;


void Fruit::setPrice(int value)

    _price = value;
    emit priceChanged(_price);

main.cpp

#include <QGuiApplication>
#include <QQuickView>
#include <QQmlContext>
#include "fruit.h"

int main(int argc, char *argv[])

    QGuiApplication app(argc, argv);
    QQuickView view;
    view.rootContext()->setContextProperty("basket", new Fruit);
    view.setSource(QStringLiteral("qml/main.qml"));
    view.show();
    return app.exec();

main.qml

import QtQuick 2.2

Rectangle 
    width:800
    height: 480
    property var obj
    color: "yellow"

    Text
        text: basket.fruit.price
        font.pixelSize: 20
    

    Component.onCompleted: 
        basket.price = 20
        console.log(basket.fruit.price)
    

【讨论】:

谢谢,但我的项目有点不同。我的Fruit 类是纯虚函数,Q_PROPERTY getter 和 setter 是虚函数。我有一个add 函数,它是 Q_INVOKABLE(也是虚拟的),我在其中添加了一个水果,然后我想访问该水果的价格。整个事情变得复杂了,这就是为什么我只发布了一些代码,但我将在今天晚些时候在帖子中发布我的代码。 我刚刚提交了完整的代码。我创建了一个新的演示,很抱歉我将上下文更改为计算机组件,我认为这更能说明我想要实现的目标。

以上是关于如何将 QObject 指针的属性公开给 QML的主要内容,如果未能解决你的问题,请参考以下文章

Qt:将指针传递给 QMimeData 中的 QObject

QML 未能检测到在 C++ 中破坏的 QObject

将 QObject 指针从 QML 对象传递给 C++

使用 QObject 属性的指针

如何通过 Q_PROPERTY 向 QML 公开指向 Q_GADGET 的指针

Qt 5.0:向 Javascript 公开 C++ 方法