Использование 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
model: myModel
delegate: Rectangle {
height: 25
width: 100
Text { text: modelData }
}
} Приложение Qt может загрузить этот документ QML и установить значение myModel в QStringList:
QStringList dataList;
dataList.append("Item 1");
dataList.append("Item 2");
dataList.append("Item 3");
dataList.append("Item 4");
QQuickView view;
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", 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;
dataList.append(new DataObject("Item 1", "red"));
dataList.append(new DataObject("Item 2", "green"));
dataList.append(new DataObject("Item 3", "blue"));
dataList.append(new DataObject("Item 4", "yellow"));
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", QVariant::fromValue(dataList));
... Объект QObject* доступен как свойство modelData. Для удобства свойства объекта также доступны непосредственно в контексте делегата. Здесь, view.qml ссылается на свойства DataModel в делегате ListView:
ListView {
width: 100; height: 100
model: myModel
delegate: Rectangle {
height: 25
width: 100
color: model.modelData.color
Text { text: 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);
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", &model);
... Эта модель отображается делегатом ListView, который обращается к ролям type и size:
ListView {
width: 200; height: 250
model: myModel
delegate: Text { 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.12/qtquick-modelviewsdata-cppmodels.html