📜 ⬆️ ⬇️

Pattern heterogeneous hierarchy of QML models

Introduction


It is often necessary to structure models as follows - at one level of a model with one structure, and at another level the structure of a model changes. For example, take the task in which you want to display a list of devices, each device has groups of settings, and each group of settings has a list of settings of various types. For simplicity, we will assume that the device has only a name and a list of groups. The group has only a name and a list of settings. The setting has only the name and type - checkbox, text box or slider.



This pattern has been systematized on the basis of the article . The following is a description of the pattern, similar to GoF.
')

Purpose


Pattern structuring the use of complex models in C ++ using QML. Facilitates the use of nested lists of models for the formation of a hierarchical structure. At the same time, for use in QML, the complexity does not increase.

Applicability


Use the pattern when:


Structure




Members



Relations


The list object delegates role data to a list item that was specified as a prototype in the constructor. It is required to be inherited either from an element with a submodel, or from a simple element of the list. In QML, to access the child model, you need to call the subModelFromId method, where the parameter is the role-id for the current element.

Code example C ++


Add a device model:

class DeviceModelItem : public Models::SubListedListItem { Q_OBJECT public: enum GroupModelItemRoles { deviceId = Qt::UserRole + 1, deviceNameRole }; DeviceModelItem(QObject* parent = 0); int id() const; QVariant data(int role) const; QHash<int, QByteArray> roleNames() const; Models::ListModel* submodel() const; private: int _id; static int g_id; QString deviceName; Models::ListModel* groupListModel; }; 

To track IDs, use the global counter g_id, the current device ID is _id. In the constructor, add a submodel and initialize the names:

 DeviceModelItem::DeviceModelItem(QObject *parent):SubListedListItem(parent), _id(g_id++) { deviceName = QString("Device %1").arg(_id); groupListModel = new Models::SubListedListModel(new GroupModelItem()); //     groupListModel->appendRow(new GroupModelItem()); groupListModel->appendRow(new GroupModelItem()); } 

Role handling:

 QVariant DeviceModelItem::data(int role) const { switch (role) { case deviceId: return this->id(); case deviceNameRole: return this->deviceName; default: return QVariant(); } } QHash<int, QByteArray> DeviceModelItem::roleNames() const { QHash<int, QByteArray> roles; roles[deviceId] = "deviceId"; roles[deviceNameRole] = "deviceName"; return roles; } 

The group model looks similar, except that in the designer we create a list without sub-models:

 GroupModelItem::GroupModelItem(QObject *parent):SubListedListItem(parent), _id(g_id++) { groupName = QString("Group %1").arg(_id); settingsListModel = new Models::ListModel(new SettingsModelItem()); ... } 

In the settings model, we already inherit from ListItem:

 class SettingsModelItem : public Models::ListItem { Q_OBJECT public: enum SettingsModelItemRoles { settingsId = Qt::UserRole + 1, settingsNameRole, settingsTypeRole } ... } 

We do not need a submodel in the settings. Now we add the root device model to the context:

 int main(int argc, char *argv[]) { //     touch QGuiApplication app(argc, argv); QQmlApplicationEngine engine; Models::ListModel* devicesModel = new Models::SubListedListModel(new DeviceModelItem()); //DEBUG devicesModel->appendRow(new DeviceModelItem()); devicesModel->appendRow(new DeviceModelItem()); devicesModel->appendRow(new DeviceModelItem()); devicesModel->appendRow(new DeviceModelItem()); devicesModel->appendRow(new DeviceModelItem()); engine.rootContext()->setContextProperty("deviceModel",devicesModel); ... } 

Sample QML Code


As the navigation model, use StackView (main.qml):

 StackView { id: stackView anchors.fill: parent initialItem: Item { width: parent.width height: parent.height ListView { model: deviceModel anchors.fill: parent delegate: AndroidDelegate { text: deviceName onClicked: stackView.push({item:Qt.resolvedUrl("pages/GroupPage.qml"), properties:{subModel:deviceModel.subModelFromId(model.deviceId)}}) } } } } 

For the groups page, set the submodel through subModelFromId. In the group model, we process it in the same way:

 ScrollView { ... property variant subModel: null ListView { ... model: subModel delegate: AndroidDelegate { text: groupName onClicked: stackView.push({item:Qt.resolvedUrl("SettingsPage.qml"), properties:{subModel:subModel.subModelFromId(model.groupId)}}) } } ... } 

For the settings page, only the list:

 ListView { id: settingsView ... model: subModel delegate: Item { CheckBox{ visible: settingsType == 0 ... } Column{ ... visible: settingsType == 1 Text{text: settingsName} TextField {text: "Text input"} } Column{ ... visible: settingsType == 2 Text{text: settingsName} Slider {value: 1.0} } } 

Result Screenshots




Link to source: GitHub
Link to article source: Article

Source: https://habr.com/ru/post/268437/


All Articles