什么是 "right way" 来表示来自 C++ 的 QML 对象的特定实例?
What is the "right way" to signal specific instances of QML objects from C++?
首先,我会道歉:这是一个怪物问题,但我想提供我希望的所有相关细节。
我有一个基于 QML 的 GUI,我的任务是接管它并从概念验证开发到发布。我相信 GUI 基于 QT(汽车,也许?)提供的示例。 GUI 正在为 web-assembly (emscripten) 编译,并具有一个“后端数据客户端”,它通过套接字与我们的硬件控制器通信,并通过信号与 GUI 通信。 GUI 通过网络浏览器访问,并通过 QWebSocket
.
与 Data_Client
通信
GUI 证明最初是使用非常“扁平”的层次结构创建的,其中每个元素都是在单个“主”QML 文件中的单个 ApplicationWindow
对象中创建和管理的。 Data_Client
对象在那里实例化,所有其他视觉元素都是 ApplicationWindow
:
的子元素(在不同级别)
ApplicationWindow {
id: appWindow
//various properties and stuff
Item {
id: clientHolder
property Data_Client client
}
ColumnLayout {
id: mainLayout
anchors.fill: parent
layoutDirection: Qt.LeftToRight
//And so on...
Data_Client
C++ 当前发出各种信号以响应控制器应用程序中发生的各种事情。在主要的 .QML 中,信号处理如下:
Connections {
target: client
onNew_status_port_data:
{
textStatusPort.text = qdata;
}
onNew_status_data_act_on:
{
imageStatusData.source = "../imagine-assets/ledGoodRim.png";
}
//and so on...
我想做的是创建一个 ChannelStatusPanel
对象,它包含各种状态字段并在从 Data_Client
后端。 MainStatusPanel
中包含此 ChannelStatusPanel
的多个实例,从主 ApplicationWindow
:
可见或不可见
说了这么多(呸!),我终于来回答我的问题了。用驱动 ChannelStatusPanel
的视觉元素变化所需的各种数据项从 Data_Client
发出 ChannelStatusPanel
对象的特定实例的正确方法是什么?
我认为我通过定义一个 ChannelStatusObject
来保存值是很聪明的:
Item {
id: channelStatusObject
property int channel
property int enabled //Using the EnabledState enum
property string mode
property int bitrate
property int dataActivity //Using the LedState enum
//and more...
property int packetCount
}
在 ChannelStatusPanel.qml
中,然后我创建了一个 ChannelStatusObject
属性 和一个插槽来处理 属性 更改:
property ChannelStatusObject statusObject
onStatusObjectChanged: {
//..do the stuff
从 Data_Client
C++ 中,我将从控制器应用程序获取信息并确定我需要更新的“通道”。在我看来,我需要能够执行以下操作:
- 我需要确定我需要更新
ChannelStatusPanel
的哪个实例。我如何智能地获取对我想要发信号的实例的引用?那是通过 QObject::findChild()
才完成的吗?有更好、更快或更聪明的方法吗?
- 在
Data_Client
C++ 中,我是否要创建 ChannelStatusObject
的实例,适当地设置其中的各个字段,然后设置 ChannelStatusPanel
实例的 ChannelStatusObject
属性等于新创建的ChannelStatusObject
?或者,是否有一种机制可以获取对面板 ChannelStatusObject
的引用并将其每个属性(字段)设置为我想要的?在 C++ 中,是这样的:
QQmlComponent component(&engine, "ChannelStatusObject.qml");
QObject *statObj= component.create();
QQmlProperty::write(statObj, "channel", 1)
QQmlProperty::write(statObj, "bitrate", 5000);
QQmlProperty::write(statObj, "enabled", 0);
//Then something like using the pointer from #1, above, to set the Panel property
//QObject *channelPanel;
QQmlProperty::write(channelPanel, "statusObject", statObj)
是否有其他更被接受或更传统的范例来执行此操作?是不是太绕了?
我会使用 Qt 的模型-视图-控制器(委托)范例来解决这个问题。也就是说,您的 C++ 代码应该公开一些类似列表 Q_PROPERTY
的通道状态对象,这些对象又将自己的数据公开为属性。这可以使用 QQmlListProperty
来完成,如 here.
所示
然而,如果列表本身由 C++ 代码控制——也就是说,QML 代码不需要直接编辑 model,而只控制显示哪些在 视图 中并可能修改现有元素——然后它可以更简单一些,例如 QObject
派生指针的 QList
。只要您在更改列表时发出信号,就可以了:
class ChannelStatus : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(int channel READ channel CONSTANT)
Q_PROPERTY(int enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
// etc.
};
class Data_Client : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(QList<ChannelStatus*> statusList READ statusList NOTIFY statusListChanged)
// ...
};
ChannelStatus class 本身必须在 QML 类型系统中注册,以便可以在 QML 文档中导入。此外,列表 属性 类型需要在主函数中或作为静态变量注册为元类型。否则,只会识别实际的 QObject
指针列表,您必须按原样提供。
qmlRegisterUncreatableType<ChannelStatus>("LibraryName", 1, 0,
"ChannelStatus", "Property access only.");
qRegisterMetaType<QList<ChannelStatus*>>();
然后您在 QML 端使用客户端的 属性 作为合适的 QML 组件的 model
属性,例如 ListView
或 Repeater
在像 RowLayout
这样的容器中。例如:
import LibraryName 1.0
ListView {
model: client.statusList
delegate: Column {
Label { text: modelData.channel }
Image { source: modelData.enabled ? "foo" : "bar" }
// ...
}
}
如您所见,模型数据隐式附加到委托组件。任何 NOTIFY
able 属性的值都会自动更新。
下面是我如何设置我的应用程序。
前端采用 QML。后端使用 Qt C++。 H/W 控制器应用程序在 C 中。
在 Qt C++ 后端,我有 QObject 派生数据库和 databasePoint classes.
数据库对象持有一个 QMap 的 databasePoint 对象。
每个 databasePoint 对象都有一个唯一的点名称,用作标识符。
数据库对象在 main.cpp 中创建并作为上下文公开给 QML 属性。
数据库 class 有一个方法 return 一个指向 databasePoint 对象的指针。
在QML中,该方法用于创建databasePoint对象。
调用此方法时,将创建一个 databasePoint 对象并将其添加到 QMap(如果尚不存在)。
databasePoint class 具有读写值属性。
这些属性用于 UI、后端和控制器之间的通信。
在计时器中,会定期从控制器轮询最新值,只要有变化,就会更新值 属性。
当值 属性 从 UI 更新时,该值被写入控制器。
class database : public QObject
{
public slots:
databasePoint* getDbpointObject(QString pointName);
private:
QMap<QString, databasePoint*> dbPointMap;
};
class databasePoint : public QObject
{
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
public:
QVariant value(void);
void setValue(QVariant value);
QVariant my_value;
signals:
void valueChanged();
};
DatabasePointComponent.qml:
Item {
required property string pointName
property var pointObj: database.getDbpointObject(pointName)
}
MyScreen.qml:
DatabasePointComponent{
id: dbTEMP
pointName: "TEMP"
}
TextInput {
text: dbTEMP.pointObj.value
onAccepted: dbTEMP.pointObj.value = text
}
首先,我会道歉:这是一个怪物问题,但我想提供我希望的所有相关细节。
我有一个基于 QML 的 GUI,我的任务是接管它并从概念验证开发到发布。我相信 GUI 基于 QT(汽车,也许?)提供的示例。 GUI 正在为 web-assembly (emscripten) 编译,并具有一个“后端数据客户端”,它通过套接字与我们的硬件控制器通信,并通过信号与 GUI 通信。 GUI 通过网络浏览器访问,并通过 QWebSocket
.
Data_Client
通信
GUI 证明最初是使用非常“扁平”的层次结构创建的,其中每个元素都是在单个“主”QML 文件中的单个 ApplicationWindow
对象中创建和管理的。 Data_Client
对象在那里实例化,所有其他视觉元素都是 ApplicationWindow
:
ApplicationWindow {
id: appWindow
//various properties and stuff
Item {
id: clientHolder
property Data_Client client
}
ColumnLayout {
id: mainLayout
anchors.fill: parent
layoutDirection: Qt.LeftToRight
//And so on...
Data_Client
C++ 当前发出各种信号以响应控制器应用程序中发生的各种事情。在主要的 .QML 中,信号处理如下:
Connections {
target: client
onNew_status_port_data:
{
textStatusPort.text = qdata;
}
onNew_status_data_act_on:
{
imageStatusData.source = "../imagine-assets/ledGoodRim.png";
}
//and so on...
我想做的是创建一个 ChannelStatusPanel
对象,它包含各种状态字段并在从 Data_Client
后端。 MainStatusPanel
中包含此 ChannelStatusPanel
的多个实例,从主 ApplicationWindow
:
说了这么多(呸!),我终于来回答我的问题了。用驱动 ChannelStatusPanel
的视觉元素变化所需的各种数据项从 Data_Client
发出 ChannelStatusPanel
对象的特定实例的正确方法是什么?
我认为我通过定义一个 ChannelStatusObject
来保存值是很聪明的:
Item {
id: channelStatusObject
property int channel
property int enabled //Using the EnabledState enum
property string mode
property int bitrate
property int dataActivity //Using the LedState enum
//and more...
property int packetCount
}
在 ChannelStatusPanel.qml
中,然后我创建了一个 ChannelStatusObject
属性 和一个插槽来处理 属性 更改:
property ChannelStatusObject statusObject
onStatusObjectChanged: {
//..do the stuff
从 Data_Client
C++ 中,我将从控制器应用程序获取信息并确定我需要更新的“通道”。在我看来,我需要能够执行以下操作:
- 我需要确定我需要更新
ChannelStatusPanel
的哪个实例。我如何智能地获取对我想要发信号的实例的引用?那是通过QObject::findChild()
才完成的吗?有更好、更快或更聪明的方法吗? - 在
Data_Client
C++ 中,我是否要创建ChannelStatusObject
的实例,适当地设置其中的各个字段,然后设置ChannelStatusPanel
实例的ChannelStatusObject
属性等于新创建的ChannelStatusObject
?或者,是否有一种机制可以获取对面板ChannelStatusObject
的引用并将其每个属性(字段)设置为我想要的?在 C++ 中,是这样的:
QQmlComponent component(&engine, "ChannelStatusObject.qml");
QObject *statObj= component.create();
QQmlProperty::write(statObj, "channel", 1)
QQmlProperty::write(statObj, "bitrate", 5000);
QQmlProperty::write(statObj, "enabled", 0);
//Then something like using the pointer from #1, above, to set the Panel property
//QObject *channelPanel;
QQmlProperty::write(channelPanel, "statusObject", statObj)
是否有其他更被接受或更传统的范例来执行此操作?是不是太绕了?
我会使用 Qt 的模型-视图-控制器(委托)范例来解决这个问题。也就是说,您的 C++ 代码应该公开一些类似列表 Q_PROPERTY
的通道状态对象,这些对象又将自己的数据公开为属性。这可以使用 QQmlListProperty
来完成,如 here.
然而,如果列表本身由 C++ 代码控制——也就是说,QML 代码不需要直接编辑 model,而只控制显示哪些在 视图 中并可能修改现有元素——然后它可以更简单一些,例如 QObject
派生指针的 QList
。只要您在更改列表时发出信号,就可以了:
class ChannelStatus : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(int channel READ channel CONSTANT)
Q_PROPERTY(int enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
// etc.
};
class Data_Client : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(QList<ChannelStatus*> statusList READ statusList NOTIFY statusListChanged)
// ...
};
ChannelStatus class 本身必须在 QML 类型系统中注册,以便可以在 QML 文档中导入。此外,列表 属性 类型需要在主函数中或作为静态变量注册为元类型。否则,只会识别实际的 QObject
指针列表,您必须按原样提供。
qmlRegisterUncreatableType<ChannelStatus>("LibraryName", 1, 0,
"ChannelStatus", "Property access only.");
qRegisterMetaType<QList<ChannelStatus*>>();
然后您在 QML 端使用客户端的 属性 作为合适的 QML 组件的 model
属性,例如 ListView
或 Repeater
在像 RowLayout
这样的容器中。例如:
import LibraryName 1.0
ListView {
model: client.statusList
delegate: Column {
Label { text: modelData.channel }
Image { source: modelData.enabled ? "foo" : "bar" }
// ...
}
}
如您所见,模型数据隐式附加到委托组件。任何 NOTIFY
able 属性的值都会自动更新。
下面是我如何设置我的应用程序。
前端采用 QML。后端使用 Qt C++。 H/W 控制器应用程序在 C 中。
在 Qt C++ 后端,我有 QObject 派生数据库和 databasePoint classes.
数据库对象持有一个 QMap 的 databasePoint 对象。
每个 databasePoint 对象都有一个唯一的点名称,用作标识符。
数据库对象在 main.cpp 中创建并作为上下文公开给 QML 属性。
数据库 class 有一个方法 return 一个指向 databasePoint 对象的指针。
在QML中,该方法用于创建databasePoint对象。
调用此方法时,将创建一个 databasePoint 对象并将其添加到 QMap(如果尚不存在)。
databasePoint class 具有读写值属性。
这些属性用于 UI、后端和控制器之间的通信。
在计时器中,会定期从控制器轮询最新值,只要有变化,就会更新值 属性。
当值 属性 从 UI 更新时,该值被写入控制器。
class database : public QObject
{
public slots:
databasePoint* getDbpointObject(QString pointName);
private:
QMap<QString, databasePoint*> dbPointMap;
};
class databasePoint : public QObject
{
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
public:
QVariant value(void);
void setValue(QVariant value);
QVariant my_value;
signals:
void valueChanged();
};
DatabasePointComponent.qml:
Item {
required property string pointName
property var pointObj: database.getDbpointObject(pointName)
}
MyScreen.qml:
DatabasePointComponent{
id: dbTEMP
pointName: "TEMP"
}
TextInput {
text: dbTEMP.pointObj.value
onAccepted: dbTEMP.pointObj.value = text
}