What is the "right way" to signal specific instances of QML objects from C++?

Solution 1:

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 NOTIFYable properties will have their values automatically updated.

Solution 2:

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
}