Использование 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::setAttribute(Qt::AA_EnableHighDpiScaling);
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-5.15/qtquick-modelviewsdata-cppmodels.html