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
ChannelStatusPanelI 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_ClientC , do I want to create an instance ofChannelStatusObject, set the various fields within it appropriately, and then set theChannelStatusPanelinstance'sChannelStatusObjectproperty equal to the newly createdChannelStatusObject? Alternatively, is there a mechanism to get a reference to the Panel'sChannelStatusObjectand 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?
CodePudding user response:
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)
// ...
};
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:
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 NOTIFYable properties will have their values automatically updated.
CodePudding user response:
Below is how I've setup my application.
Front-end is in QML. Back-end is in Qt C . H/W Controller application is in C.
In Qt C back-end, I have QObject derived database and databasePoint classes.
database object holds a QMap of databasePoint objects.
Each databasePoint object has a unique point name, which is used as an identifier.
database object is created in main.cpp and exposed to QML as a context property.
database class has a method to return a pointer to databasePoint object.
In QML, this method is used to create databasePoint objects.
When this method is called, a databasePoint object is created and added to QMap if it doesn't already exist.
databasePoint class has read-write value properties.
These properties are used for communication between UI, back-end and controller.
In a timer, latest value is polled from controller periodically, whenever there's a change, the value property is updated.
When the value property is updated from UI, the value is written to controller.
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
}


