如何将 QObject 指针的 属性 暴露给 QML
How to expose property of QObject pointer to QML
我对我的 class 进行了非常简短(和部分)的描述,以说明我的问题。基本上我设置了两个属性。
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
属性,它运行良好。但是,如果我访问显然是 returns Fruit
的 fruit
属性,然后尝试使用它的 price
属性,那是行不通的。这不应该这样工作吗?
Text {
id: myText
anchors.centerIn: parent
text: basket.price // shows correctly
//text: basket.fruit.price // doesn't show
}
第二个 returns Fruit
也是一个 属性 并且它有 price
属性 但它似乎无法访问 属性?这应该有效吗?
已更新
我包括我的源代码。我用 HardwareComponent
创建了一个新演示,这样更有意义。我试图根据收到的答案让它工作,但没有成功。
HardwareComponent
class 是 Computer
和 CPU
.
的基础 class
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
}
}
这是我所有项目文件的完整源代码。
当我 运行 它时,我得到这个输出:
Starting C:\Users\User\Documents\My Qt
Projects\build-hardware-Desktop_Qt_5_4_0_MSVC2010_OpenGL_32bit-Debug\debug\hardware.exe...
QML debugging is enabled. Only use this in a safe environment.
qrc:/MainForm.ui.qml:20: ReferenceError: computer is not defined
即使它在第 20 行 (computer.price) 行发出警告,它仍然有效并显示计算机的价格 (=500)。如果更改它 computer.cpu.price
,会报告相同的警告,但不再显示价格 - 它似乎不起作用。
问题是因为价格是虚拟的 属性,它有效!但是如果我在计算机组件内部的另一个硬件组件上使用这个属性,它就不起作用了! Mido 发布的 code/answer 让我希望可以解决这个问题,看起来很接近!我希望我能完成这项工作。
我像您的示例一样创建了一个 Fruit
class,它工作正常。我创建了一个 Fruit
的新实例,我可以获得 price
值。这是我的代码:
fruit.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
fruit.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)
}
}
只有几处错误,具体与 QML 相关:
必须在引擎加载 qml 文件之前设置上下文属性。
您还没有注册HardwareComponent
类型。有关详细信息,请参阅 Defining QML Types from C++。
除了 QML 方面,您似乎希望硬件是具有树结构的复合体。由于 QObject
已经是一个组合,您可以利用它来发挥自己的优势。硬件项目树可以是objects的树。 QObject
在添加或删除 children 时通知 parents:因此在添加和删除 children 时很容易保持当前价格。
每个组件都有一个unitPrice
:这是组件单独的价格。 price
是组件的单价和它的 children 价格的总和。
您可以缓存它并仅在单价变化或 child 价格变化时更新它,而不是遍历整个树来获取总价。同样,当前的 CPU 可以被缓存,您可以在任何给定时间强制执行单个 CPU,等等。该示例是实用的,但只是一个最小的草图。
我还展示了如何更改计算机中的 CPU 类型。价格变化和 cpu 变化都有通知。 QML UI 在 cpu
属性 发生变化时以及任何 price
属性 发生变化时做出适当的反应。
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) }
}
}
}
我对我的 class 进行了非常简短(和部分)的描述,以说明我的问题。基本上我设置了两个属性。
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
属性,它运行良好。但是,如果我访问显然是 returns Fruit
的 fruit
属性,然后尝试使用它的 price
属性,那是行不通的。这不应该这样工作吗?
Text {
id: myText
anchors.centerIn: parent
text: basket.price // shows correctly
//text: basket.fruit.price // doesn't show
}
第二个 returns Fruit
也是一个 属性 并且它有 price
属性 但它似乎无法访问 属性?这应该有效吗?
已更新
我包括我的源代码。我用 HardwareComponent
创建了一个新演示,这样更有意义。我试图根据收到的答案让它工作,但没有成功。
HardwareComponent
class 是 Computer
和 CPU
.
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
}
}
这是我所有项目文件的完整源代码。
当我 运行 它时,我得到这个输出:
Starting C:\Users\User\Documents\My Qt Projects\build-hardware-Desktop_Qt_5_4_0_MSVC2010_OpenGL_32bit-Debug\debug\hardware.exe... QML debugging is enabled. Only use this in a safe environment. qrc:/MainForm.ui.qml:20: ReferenceError: computer is not defined
即使它在第 20 行 (computer.price) 行发出警告,它仍然有效并显示计算机的价格 (=500)。如果更改它 computer.cpu.price
,会报告相同的警告,但不再显示价格 - 它似乎不起作用。
问题是因为价格是虚拟的 属性,它有效!但是如果我在计算机组件内部的另一个硬件组件上使用这个属性,它就不起作用了! Mido 发布的 code/answer 让我希望可以解决这个问题,看起来很接近!我希望我能完成这项工作。
我像您的示例一样创建了一个 Fruit
class,它工作正常。我创建了一个 Fruit
的新实例,我可以获得 price
值。这是我的代码:
fruit.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
fruit.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)
}
}
只有几处错误,具体与 QML 相关:
必须在引擎加载 qml 文件之前设置上下文属性。
您还没有注册
HardwareComponent
类型。有关详细信息,请参阅 Defining QML Types from C++。
除了 QML 方面,您似乎希望硬件是具有树结构的复合体。由于 QObject
已经是一个组合,您可以利用它来发挥自己的优势。硬件项目树可以是objects的树。 QObject
在添加或删除 children 时通知 parents:因此在添加和删除 children 时很容易保持当前价格。
每个组件都有一个unitPrice
:这是组件单独的价格。 price
是组件的单价和它的 children 价格的总和。
您可以缓存它并仅在单价变化或 child 价格变化时更新它,而不是遍历整个树来获取总价。同样,当前的 CPU 可以被缓存,您可以在任何给定时间强制执行单个 CPU,等等。该示例是实用的,但只是一个最小的草图。
我还展示了如何更改计算机中的 CPU 类型。价格变化和 cpu 变化都有通知。 QML UI 在 cpu
属性 发生变化时以及任何 price
属性 发生变化时做出适当的反应。
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) }
}
}
}