Хотя встроенные типы моделей Qt Quick покрывают большинство распространенных ситуаций, тем не менее в каких-то сложных, комплексных сценариях их возможностей может оказаться недостаточно. И в этом случае мы можем определить свои классы моделей на C++ и использовать их в QML.
Класс модели C++ может быть определен как объект следующих классов:
QStringList: содержит список экземпляров QString и предоставляет содержимое списка через свойство modelData
QVariantList: содержит список экземпляров QVariant и предоставляет содержимое списка через свойство modelData
QObjectList: содержит список экземпляров QObject*
, который предоставляет свойства объектов в списке в качестве ролей. QObject* доступен как свойство modelData
QAbstractItemModel: содержит произвольную структуру данных
Первые три применяются для предоставления более простых наборов данных, а QAbstractItemModel обеспечивает более гибкое решение для более сложных моделей.
Например, возьмем самый простой тип - QStringList, который представляет список строк QString. В файле main.qml определим простейший интерфейс:
import QtQuick Window { width: 250 height: 150 visible: true title: "METANIT.COM" ListView { objectName: "listView" anchors.fill: parent delegate: Text { text: modelData } } }
Здесь для ListView НЕ устанавливается модель, однако с помощью делегата для каждого элемента модели будет создаваться объект Text.
В файле main.cpp определим саму модель и подключим ее к ListView:
#include <QGuiApplication> #include <QQmlApplicationEngine> int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url("qrc:/path/main.qml"); engine.load(url); // модель QStringList dataList = { "Tom", "Bob", "Sam", "Alice" }; // получаем listView QObject* listView = engine.rootObjects().first()->findChild<QObject*>("listView"); // устанавливаем модель listView->setProperty("model", dataList); return app.exec(); }
Здесь для свойства "model" элемента ListView устанавливается список из 4 строк dataList:
Рассмотрим чуть более сложный пример - создание своей модели с помощью QAbstractItemModel.
Это отптимальный подход, если необходима более сложная модель, которую невозможно опредилить с помощью других подходов. QAbstractItemModel
также может
автоматически уведомлять представление QML при изменении данных модели.
Чтобы упростить определение функционала модели, вместо QAbstractItemModel мы можем реализовать один из классов-наследников - QAbstractListModel или QAbstractTableModel, если нам надо создать представление данных в виде списка или таблицы соответственно.
Итак, рассмотрим, как создадим кастомную модель, которая представляет список. Для этого используем класс QAbstractListModel. Пусть у нас есть следующий проект:
Прежде всего в файлах person.h и person.cpp расположены определение и реализация класса Person, который будет представлять отдельный элемент данных. Определение в файле person.h:
#ifndef PERSON_H #define PERSON_H #includeclass Person { public: Person(const std::string &name, const int &age); std::string name() const; int age() const; private: std::string m_name; int m_age; }; #endif // PERSON_H
Реализация класса в person.cpp:
#include "person.h" Person::Person(const std::string &name, const int &age) : m_name(name), m_age(age) {} std::string Person::name() const { return m_name; } int Person::age() const { return m_age; }
Таким образом, класс Person представляет пользователя и хранит данные в полях m_name (имя) и m_age (возраст). Эти поля устанавливаются через конструктор, а с помощью методов name()
и age()
можно возвратить их значения.
Для работы с данными этого класса определим класс модели PersonModel, которая будет располагаться в файлах personmodel.h и personmodel.cpp. В заголовочном файле personmodel.h будет следующее определение модели PersonModel:
#ifndef PERSONMODEL_H #define PERSONMODEL_H #include <QString> #include <QAbstractListModel> #include "person.h" class PersonModel : public QAbstractListModel { Q_OBJECT public: enum PersonRoles { NameRole = Qt::UserRole + 1, AgeRole }; PersonModel(QObject *parent = nullptr); void addPerson(const Person &person); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; protected: QHash<int, QByteArray> roleNames() const; private: QList<Person> people; }; #endif // PERSONMODEL_H
Класс модели применяет макрос Q_OBJECT. Для упрощения обращения к функциональности объекта Person - к имени и возрасту пользователя определено перечисление PersonRoles, которое хранит числовые идентификаторы ролей. По этому идентификатору мы сможем узнать, какое поле запрошено - имя или возраст:
enum PersonRoles { NameRole = Qt::UserRole + 1, AgeRole };
Для добавления одного объекта Person определен метод addPerson
. А все добавляемые в модели объекты Person будут храниться в переменной people.
Чтобы определить свой класс модели, надо реализовать ряд функций базового класса QAbstractListModel. В частности,
rowCount()
: возвращает количество строк (элементов списка)
data()
: возвращает данные по определенному индексу
roleNames()
: возвращает имена ролей в виде хеш-таблицы QHash<int, QByteArray>
В файле personmodel.cpp определим реализацию методов модели:
#include "personmodel.h" PersonModel::PersonModel(QObject *parent) : QAbstractListModel(parent) { } void PersonModel::addPerson(const Person &Person) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); people << Person; endInsertRows(); } int PersonModel::rowCount(const QModelIndex & parent) const { Q_UNUSED(parent); return people.count(); } QVariant PersonModel::data(const QModelIndex & index, int role) const { if (index.row() < 0 || index.row() >= people.count()) return QVariant(); const Person &Person = people[index.row()]; if (role == NameRole) return QString::fromStdString(Person.name()); else if (role == AgeRole) return QVariant::fromValue(Person.age()); return QVariant(); } QHash<int, QByteArray> PersonModel::roleNames() const { QHash<int, QByteArray> roles; roles[NameRole] = "name"; roles[AgeRole] = "age"; return roles; }
В методе addPerson()
добавляем один объект Person в список people в модели. Согласно документации, добавление данных должно располагаться между вызовами
beginInsertRows()
и endInsertRows()
void PersonModel::addPerson(const Person &Person) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); people << Person; endInsertRows(); }
В методе rowCount возвращаем количество элементов в модели:
int PersonModel::rowCount(const QModelIndex & parent) const { Q_UNUSED(parent); return people.count(); }
В методе data()
по переданному индексу QModelIndex получаем нужную строку (объект Person), а числовой иддентификатор роли, что именно мы хотим получить имя или возраст:
QVariant PersonModel::data(const QModelIndex & index, int role) const { if (index.row() < 0 || index.row() >= people.count()) return QVariant(); const Person &Person = people[index.row()]; if (role == NameRole) return QString::fromStdString(Person.name()); else if (role == AgeRole) return QVariant::fromValue(Person.age()); return QVariant(); }
В методе roleNames формируем хеш-таблицу, где ключами выступают идентификаторы ролей, а значениями - их строковые описания:
QHash<int, QByteArray> PersonModel::roleNames() const { QHash<int, QByteArray> roles; roles[NameRole] = "name"; roles[AgeRole] = "age"; return roles; }
В файле main.qml определим ListView для вывода данных:
import QtQuick Window { width: 250 height: 150 visible: true title: "METANIT.COM" ListView { objectName: "listView" anchors.fill: parent delegate: Text { text: name + " (" + age + ")" } } }
И в файле main.cpp создадим модель и свяжем ее с ListView:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "personmodel.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url("qrc:/path/main.qml"); engine.load(url); // создаем модель и определяем данные PersonModel model; model.addPerson(Person("Tom", 39)); model.addPerson(Person("Bob", 43)); model.addPerson(Person("Sam", 28)); // устанавливаем модель для ListView QObject* listView = engine.rootObjects().first()->findChild<QObject*>("listView"); listView->setProperty("model", QVariant::fromValue(&model)); return app.exec(); }
В примере выше для установки модели мы находили элемент по имени (которое устанавливается свойством objectName) и и затем у найденного элемент устанавливали свойство model
QObject* listView = engine.rootObjects().first()->findChild<QObject*>("listView"); listView->setProperty("model", QVariant::fromValue(&model));
Также можно использовать другие способы. Например, передать модель через свойства контекста в main.cpp:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> // для свойств контекста #include "personmodel.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url("qrc:/path/main.qml"); // создаем модель и определяем данные PersonModel model; model.addPerson(Person("Tom", 39)); model.addPerson(Person("Bob", 43)); model.addPerson(Person("Sam", 28)); // передаем свойство контекста engine.rootContext()->setContextProperty("users", QVariant::fromValue(&model)); // свойство контекста устанавливается перед load engine.load(url); return app.exec(); }
Здесь устанавливается свойство "users". В коде QML мы можем передать его значение модели ListView:
ListView { model: users anchors.fill: parent delegate: Text { text: name + " (" + age + ")" } }