Использование C++ моделей с Qt Quick Views
Данные, предоставляемые в пользовательской C++ модели
Модели могут быть определены на C++ и затем предоставлены в QML. Это полезно для экспонирования существующих C++ моделей данных или других сложных наборов данных в QML.
Класс модели C++ может быть определен как QStringList, QVariantList, QObjectList или QAbstractItemModel. Первые три полезны для экспонирования более простых наборов данных, в то время как QAbstractItemModel предоставляет более гибкое решение для более сложных моделей.
Вот обучающее видео, которое проведет вас через весь процесс экспонирования C++ модели в QML:
Модель на основе QStringList
Модель может быть простой QStringList, которая предоставляет содержимое списка через роль modelData.
Вот ListView с делегатом, который ссылается на значение элемента модели с помощью роли modelData:
ListView {
width: 100
height: 100
required model
delegate: Rectangle {
required property string modelData
height: 25
width: 100
Text { text: parent.modelData }
}
} Приложение Qt может загрузить этот QML документ и установить значение myModel в QStringList:
QStringList dataList = {
"Item 1",
"Item 2",
"Item 3",
"Item 4"
};
QQuickView view;
view.setInitialProperties({{ "model", QVariant::fromValue(dataList) }}); Полный исходный код этого примера доступен в examples/quick/models/stringlistmodel в каталоге установки Qt.
Примечание: Способ, которым представление узнает о том, что содержимое QStringList изменилось, отсутствует. Если QStringList изменяется, необходимо сбросить модель, вызвав QQmlContext::setContextProperty() снова.
Модель на основе QVariantList
Модель может быть единственным QVariantList, который предоставляет содержимое списка через роль modelData.
API работает так же, как и с QStringList, как показано в предыдущем разделе.
Примечание: Способ, которым представление узнает о том, что содержимое QVariantList изменилось, отсутствует. Если QVariantList изменяется, необходимо сбросить модель.
Модель на основе QObjectList
Список значений QObject* также может быть использован в качестве модели. QList<QObject*> предоставляет свойства объектов в списке в виде ролей.
Следующее приложение создает класс DataObject, со значениями Q_PROPERTY, которые будут доступны как именованные роли, когда QList<DataObject*> экспонируется в QML:
class DataObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)
...
};
int main(int argc, char ** argv)
{
QGuiApplication app(argc, argv);
QList<QObject *> dataList = {
new DataObject("Item 1", "red"),
new DataObject("Item 2", "green"),
new DataObject("Item 3", "blue"),
new DataObject("Item 4", "yellow")
};
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setInitialProperties({{ "model", QVariant::fromValue(dataList) }});
... Значение QObject* доступно как свойство modelData. Для удобства свойства объекта также доступны непосредственно в контексте делегата. Здесь view.qml ссылается на свойства DataModel в делегате ListView:
ListView {
width: 100; height: 100
required model
delegate: Rectangle {
required color
required property string name
height: 25
width: 100
Text { text: parent.name }
}
} Обратите внимание на использование свойства color с квалификатором. Свойства объекта не дублируются в объекте model, так как они легко доступны через объект modelData.
Полный исходный код этого примера доступен в examples/quick/models/objectlistmodel в каталоге установки Qt.
Примечание: Способ, которым представление узнает о том, что содержимое QList изменилось, отсутствует. Если QList изменяется, необходимо сбросить модель, вызвав QQmlContext::setContextProperty() снова.
Подкласс QAbstractItemModel
Модель может быть определена путем наследования от QAbstractItemModel. Это лучший подход, если у вас более сложная модель, которую нельзя поддерживать другими методами. QAbstractItemModel также может автоматически уведомлять QML представление об изменении данных модели.
Роли подкласса QAbstractItemModel могут быть экспонированы в QML путем переопределения QAbstractItemModel::roleNames().
Вот приложение с подклассом QAbstractListModel с именем AnimalModel, которое экспонирует роли type и sizes. Оно переопределяет QAbstractItemModel::roleNames() для экспонирования имен ролей, чтобы к ним можно было получить доступ через QML:
class Animal
{
public:
Animal(const QString &type, const QString &size);
...
};
class AnimalModel : public QAbstractListModel
{
Q_OBJECT
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AnimalModel(QObject *parent = 0);
...
};
QHash<int, QByteArray> AnimalModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
int main(int argc, char ** argv)
{
QGuiApplication app(argc, argv);
AnimalModel model;
model.addAnimal(Animal("Wolf", "Medium"));
model.addAnimal(Animal("Polar bear", "Large"));
model.addAnimal(Animal("Quoll", "Small"));
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setInitialProperties({{"model", QVariant::fromValue(&model)}});
... Эта модель отображается делегатом ListView, который обращается к ролям type и size:
ListView {
width: 200; height: 250
required model
delegate: Text {
required property string type
required property string size
text: "Animal: " + type + ", " + size
}
} QML представления автоматически обновляются при изменении модели. Помните, что модель должна следовать стандартным правилам изменения модели и уведомлять представление об изменениях, используя QAbstractItemModel::dataChanged(), QAbstractItemModel::beginInsertRows() и т.д. Дополнительную информацию см. в справочнике по наследованию модели Model subclassing reference.
Полный исходный код этого примера доступен в examples/quick/models/abstractitemmodel в каталоге установки Qt.
QAbstractItemModel представляет собой иерархию таблиц, но представления, которые в настоящее время предоставляются QML, могут отображать только данные списка. Для отображения дочерних списков иерархической модели используйте тип QML DelegateModel, который предоставляет следующие свойства и функции для использования со списковыми моделями типа QAbstractItemModel:
- Свойство роли hasModelChildren для определения, имеет ли узел дочерние узлы.
- DelegateModel::rootIndex позволяет указать корневой узел.
- DelegateModel::modelIndex() возвращает QModelIndex, который можно назначить свойству DelegateModel::rootIndex.
- DelegateModel::parentModelIndex() возвращает QModelIndex, который можно назначить свойству DelegateModel::rootIndex.
SQL Модели
Qt предоставляет классы C++, которые поддерживают SQL модели данных. Эти классы работают прозрачно с базовыми SQL данными, сводя к минимуму необходимость выполнения SQL запросов для основных операций SQL, таких как создание, вставка или обновление. Более подробную информацию об этих классах см. в разделе Использование классов SQL модели.
Хотя классы C++ предоставляют полные наборы функций для работы с SQL данными, они не предоставляют доступ к данным QML. Поэтому необходимо реализовать пользовательскую C++ модель данных в виде подкласса одного из этих классов и экспонировать ее в QML либо как тип, либо как свойство контекста.
Модель данных только для чтения
Пользовательская модель должна переопределить следующие методы для разрешения только для чтения доступа к данным из QML:
-
roleNames() для экспонирования имен ролей в QML фронтэнде. Например, следующая версия возвращает имена полей выбранной таблицы как имена ролей:
QHash<int, QByteArray> SqlQueryModel::roleNames() const { QHash<int, QByteArray> roles; // record() returns an empty QSqlRecord for (int i = 0; i < this->record().count(); i ++) { roles.insert(Qt::UserRole + i + 1, record().fieldName(i).toUtf8()); } return roles; } -
data() для экспонирования SQL данных в QML фронтэнде. Например, следующая реализация возвращает данные для данного индекса модели:
QVariant SqlQueryModel::data(const QModelIndex &index, int role) const { QVariant value; if (index.isValid()) { if (role < Qt::UserRole) { value = QSqlQueryModel::data(index, role); } else { int columnIdx = role - Qt::UserRole - 1; QModelIndex modelIndex = this->index(index.row(), columnIdx); value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole); } } return value; }
Класс QSqlQueryModel достаточно хорош для реализации пользовательской модели только для чтения, представляющей данные в базе данных SQL. Пример учебника по чату chat tutorial очень хорошо демонстрирует это, реализуя пользовательскую модель для извлечения контактной информации из базы данных SQLite.
Редактируемая модель данных
QSqlTableModel реализует setData(), как описано ниже.
В зависимости от используемой моделью стратегии редактирования (EditStrategy), изменения либо помещаются в очередь для последующей отправки, либо отправляются немедленно.
Также можно вставлять новые данные в модель, вызывая QSqlTableModel::insertRecord(). В следующем фрагменте примера QSqlRecord заполняется подробностями книги и добавляется в модель:
...
QSqlRecord newRecord = record();
newRecord.setValue("author", "John Grisham");
newRecord.setValue("booktitle", "The Litigators");
insertRecord(rowCount(), newRecord);
... Экспонирование C++ моделей данных в QML
В приведенных выше примерах используется QQmlContext::setContextProperty() для установки значений модели непосредственно в QML компонентах. Альтернативой этому является регистрация класса C++ модели как типа QML (либо прямо из точки входа C++, или в функции инициализации QML C++ плагина, как показано ниже). Это позволит создавать классы моделей непосредственно как типы в QML:
| C++ |
class MyModelPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.QmlExtension.MyModel" FILE "mymodel.json")
public:
void registerTypes(const char *uri)
{
qmlRegisterType<MyModel>(uri, 1, 0,
"MyModel");
}
} |
| QML |
MyModel {
id: myModel
ListElement { someProperty: "some value" }
} ListView {
width: 200; height: 250
model: myModel
delegate: Text { text: someProperty }
} |
Подробности о написании QML C++ плагинов см. в разделе Написание QML расширений с помощью C++.
Изменение данных модели
Помимо roleNames() и data(), модели, допускающие редактирование, должны переопределять метод setData для сохранения изменений в существующих данных модели. В следующей версии метода проверяется, является ли заданный индекс модели допустимым и роль role равна Qt::EditRole:
bool EditableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
// Set data in model here. It can also be a good idea to check whether
// the new value actually differs from the current value
if (m_entries[index.row()] != value.toString()) {
m_entries[index.row()] = value.toString();
emit dataChanged(index, index, { Qt::EditRole, Qt::DisplayRole });
return true;
}
}
return false;
} Примечание: Важно выпустить сигнал dataChanged() после сохранения изменений.
В отличие от C++ представлений элементов, таких как QListView или QTableView, метод setData() должен быть явно вызван из QML делегатов всякий раз, когда это уместно. Это делается путем простого присвоения нового значения соответствующему свойству модели.
ListView {
anchors.fill: parent
model: EditableModel {}
delegate: TextField {
width: ListView.view.width
text: model.edit
onAccepted: model.edit = text
}
} Примечание: Роль edit равна Qt::EditRole. См. roleNames() для встроенных имен ролей. Однако в реальных моделях обычно регистрируются пользовательские роли.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.2/qtquick-modelviewsdata-cppmodels.html