Issue
Right up-front, I’ll apologize: This is a monster question, but I wanted to provide what I hope is all of the pertinent details.
I’ve got a QML-based GUI that I was tasked with taking over and developing from proof-of-concept to release. I believe the GUI is based on an example provided by QT (Automotive, maybe?). The GUI is being compiled for web-assembly (emscripten) and features a "back-end data-client" which communicates with our hardware controller via a socket and communicates with the GUI via signals. The GUI is accessed via web browser and communicates with the Data_Client
via QWebSocket
.
The GUI proof was initially created with a very "flat" hierarchy where every element is created and managed within a single ApplicationWindow
object in a single "main" QML file. A Data_Client
object is instantiated there and all the other visual elements are children (at various levels) of the 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...
The Data_Client
C++ currently emits various signals in response to various things that happen in the controller application. In the main .QML the signals are handled as follows:
Connections {
target: client
onNew_status_port_data:
{
textStatusPort.text = qdata;
}
onNew_status_data_act_on:
{
imageStatusData.source = "../imagine-assets/ledGoodRim.png";
}
//and so on...
What I’m trying to do is create a ChannelStatusPanel
object that holds the various status fields and handles the updates to those fields (text, images, etc.) when it receives information from the Data_Client
backend. There are multiple instances of this ChannelStatusPanel
contained in a MainStatusPanel
which is made visible or not from the main ApplicationWindow
:
Having said all of that (Phew!), I come finally to my question(s). What is the correct way to signal a specific instance of the ChannelStatusPanel
object from the Data_Client
with the various data items needed to drive changes to the visual elements of the ChannelStatusPanel
?
I thought I was being clever by defining a ChannelStatusObject
to hold the values:
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
}
In the ChannelStatusPanel.qml
, I then created a ChannelStatusObject
property and a slot to handle the property change:
property ChannelStatusObject statusObject
onStatusObjectChanged: {
//..do the stuff
From the Data_Client
C++ I will get the information from the controller application and determine which "channel" I need to update. As I see it, I need to be able to do the following things:
- I need to determine which instance of
ChannelStatusPanel
I need to update. How do I intelligently get a reference to the instance I want to signal? Is that just accomplished throughQObject::findChild()
? Is there a better, faster, or smarter way? - In the
Data_Client
C++, do I want to create an instance ofChannelStatusObject
, set the various fields within it appropriately, and then set theChannelStatusPanel
instance’sChannelStatusObject
property equal to the newly createdChannelStatusObject
? Alternatively, is there a mechanism to get a reference to the Panel’sChannelStatusObject
and set each of its properties (fields) to what I want? In C++, something like this:
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)
Is there some other, more accepted or conventional paradigm for doing this? Is this too convoluted?
Solution
I would go about this using Qt’s model-view-controller (delegate) paradigm. That is, your C++ code should expose some list-like Q_PROPERTY
of channel status objects, which in turn expose their own data as properties. This can be done using a QQmlListProperty
, as demonstrated here.
However, if the list itself is controlled from C++ code — that is, the QML code does not need to directly edit the model, but only control which ones are shown in the view and possibly modify existing elements — then it can be something simpler like a QList
of QObject
-derived pointers. As long as you do emit a signal when changing the list, this should be fine:
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)
// ...
};
The ChannelStatus class itself must be registered with the QML type system, so that it can be imported in QML documents. Additionally, the list property type will need to be registered as a metatype, either in the main function or as a static variable. Otherwise, only lists of actual QObject
pointers are recognised and you would have to provide yours as such.
qmlRegisterUncreatableType<ChannelStatus>("LibraryName", 1, 0,
"ChannelStatus", "Property access only.");
qRegisterMetaType<QList<ChannelStatus*>>();
You then use this property of the client on the QML side as the model
property of a suitable QML component, such as a ListView
or a Repeater
inside a container like RowLayout
. For example:
import LibraryName 1.0
ListView {
model: client.statusList
delegate: Column {
Label { text: modelData.channel }
Image { source: modelData.enabled ? "foo" : "bar" }
// ...
}
}
As you can see, the model data is implicitly attached to the delegate components. Any NOTIFY
able properties will have their values automatically updated.
Answered By – sigma
Answer Checked By – Senaida (BugsFixing Volunteer)