Использование 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.13/qtquick-modelviewsdata-cppmodels.html