Использование моделей 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() и так далее. Более подробная информация представлена в разделе Справочник по наследованию от моделей.
Полный исходный код этого примера доступен в 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. Пример учебного пособия по чату здесь демонстрирует это очень хорошо, реализуя пользовательскую модель для извлечения данных контактов из базы данных 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.0/qtquick-modelviewsdata-cppmodels.html