Классы C++ в виде моделей для QML

Последнее обновление: 12.01.2024

Хотя встроенные типы моделей Qt Quick покрывают большинство распространенных ситуаций, тем не менее в каких-то сложных, комплексных сценариях их возможностей может оказаться недостаточно. И в этом случае мы можем определить свои классы моделей на C++ и использовать их в QML.

Класс модели C++ может быть определен как объект следующих классов:

  • QStringList: содержит список экземпляров QString и предоставляет содержимое списка через свойство modelData

  • QVariantList: содержит список экземпляров QVariant и предоставляет содержимое списка через свойство modelData

  • QObjectList: содержит список экземпляров QObject*, который предоставляет свойства объектов в списке в качестве ролей. QObject* доступен как свойство modelData

  • QAbstractItemModel: содержит произвольную структуру данных

Первые три применяются для предоставления более простых наборов данных, а QAbstractItemModel обеспечивает более гибкое решение для более сложных моделей.

Например, возьмем самый простой тип - QStringList, который представляет список строк QString. В файле main.qml определим простейший интерфейс:

import QtQuick

Window {
    width: 250
    height: 150
    visible: true
    title: "METANIT.COM"

    ListView {
        objectName: "listView"
        anchors.fill: parent
        delegate: Text { text: modelData }
    }
}

Здесь для ListView НЕ устанавливается модель, однако с помощью делегата для каждого элемента модели будет создаваться объект Text.

В файле main.cpp определим саму модель и подключим ее к ListView:

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;


    const QUrl url("qrc:/path/main.qml");

    engine.load(url);

    // модель
    QStringList dataList = {
        "Tom",
        "Bob",
        "Sam",
        "Alice"
    };
    // получаем listView
    QObject* listView = engine.rootObjects().first()->findChild<QObject*>("listView");
    // устанавливаем модель
    listView->setProperty("model", dataList);

    return app.exec();
}

Здесь для свойства "model" элемента ListView устанавливается список из 4 строк dataList:

QStringList в качестве модели в QML и Qt Quick

Создание своей модели

Рассмотрим чуть более сложный пример - создание своей модели с помощью QAbstractItemModel. Это отптимальный подход, если необходима более сложная модель, которую невозможно опредилить с помощью других подходов. QAbstractItemModel также может автоматически уведомлять представление QML при изменении данных модели.

Чтобы упростить определение функционала модели, вместо QAbstractItemModel мы можем реализовать один из классов-наследников - QAbstractListModel или QAbstractTableModel, если нам надо создать представление данных в виде списка или таблицы соответственно.

Итак, рассмотрим, как создадим кастомную модель, которая представляет список. Для этого используем класс QAbstractListModel. Пусть у нас есть следующий проект:

Создание своего класса модели на основе QAbstractItemModel в QML и Qt Quick

Прежде всего в файлах person.h и person.cpp расположены определение и реализация класса Person, который будет представлять отдельный элемент данных. Определение в файле person.h:

#ifndef PERSON_H
#define PERSON_H

#include 

class Person
{
public:
    Person(const std::string &name, const int &age);

    std::string name() const;
    int age() const;

private:
    std::string m_name;
    int m_age;
};

#endif // PERSON_H

Реализация класса в person.cpp:

#include "person.h"

Person::Person(const std::string &name, const int &age)
    : m_name(name), m_age(age) {}

std::string Person::name() const { return m_name; }

int Person::age() const { return m_age; }

Таким образом, класс Person представляет пользователя и хранит данные в полях m_name (имя) и m_age (возраст). Эти поля устанавливаются через конструктор, а с помощью методов name() и age() можно возвратить их значения.

Для работы с данными этого класса определим класс модели PersonModel, которая будет располагаться в файлах personmodel.h и personmodel.cpp. В заголовочном файле personmodel.h будет следующее определение модели PersonModel:

#ifndef PERSONMODEL_H
#define PERSONMODEL_H

#include <QString>
#include <QAbstractListModel>
#include "person.h"

class PersonModel : public QAbstractListModel
{
    Q_OBJECT
public:
    enum PersonRoles {
        NameRole = Qt::UserRole + 1,
        AgeRole
    };

    PersonModel(QObject *parent = nullptr);

    void addPerson(const Person &person);

    int rowCount(const QModelIndex &parent = QModelIndex()) const;

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;

protected:
    QHash<int, QByteArray> roleNames() const;
private:
    QList<Person> people;
};

#endif // PERSONMODEL_H

Класс модели применяет макрос Q_OBJECT. Для упрощения обращения к функциональности объекта Person - к имени и возрасту пользователя определено перечисление PersonRoles, которое хранит числовые идентификаторы ролей. По этому идентификатору мы сможем узнать, какое поле запрошено - имя или возраст:

enum PersonRoles {
    NameRole = Qt::UserRole + 1,
    AgeRole
};

Для добавления одного объекта Person определен метод addPerson. А все добавляемые в модели объекты Person будут храниться в переменной people.

Чтобы определить свой класс модели, надо реализовать ряд функций базового класса QAbstractListModel. В частности,

  • rowCount(): возвращает количество строк (элементов списка)

  • data(): возвращает данные по определенному индексу

  • roleNames(): возвращает имена ролей в виде хеш-таблицы QHash<int, QByteArray>

В файле personmodel.cpp определим реализацию методов модели:

#include "personmodel.h"

PersonModel::PersonModel(QObject *parent)
    : QAbstractListModel(parent) { }

void PersonModel::addPerson(const Person &Person)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    people << Person;
    endInsertRows();
}

int PersonModel::rowCount(const QModelIndex & parent) const {
    Q_UNUSED(parent);
    return people.count();
}

QVariant PersonModel::data(const QModelIndex & index, int role) const {
    if (index.row() < 0 || index.row() >= people.count()) return QVariant();

    const Person &Person = people[index.row()];
    if (role == NameRole)
        return QString::fromStdString(Person.name());
    else if (role == AgeRole)
        return QVariant::fromValue(Person.age());
    return QVariant();
}

QHash<int, QByteArray> PersonModel::roleNames() const {
    QHash<int, QByteArray> roles;
    roles[NameRole] = "name";
    roles[AgeRole] = "age";
    return roles;
}

В методе addPerson() добавляем один объект Person в список people в модели. Согласно документации, добавление данных должно располагаться между вызовами beginInsertRows() и endInsertRows()

void PersonModel::addPerson(const Person &Person)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    people << Person;
    endInsertRows();
}

В методе rowCount возвращаем количество элементов в модели:

int PersonModel::rowCount(const QModelIndex & parent) const {
    Q_UNUSED(parent);
    return people.count();
}

В методе data() по переданному индексу QModelIndex получаем нужную строку (объект Person), а числовой иддентификатор роли, что именно мы хотим получить имя или возраст:

QVariant PersonModel::data(const QModelIndex & index, int role) const {
    if (index.row() < 0 || index.row() >= people.count()) return QVariant();

    const Person &Person = people[index.row()];
    if (role == NameRole)
        return QString::fromStdString(Person.name());
    else if (role == AgeRole)
        return QVariant::fromValue(Person.age());
    return QVariant();
}

В методе roleNames формируем хеш-таблицу, где ключами выступают идентификаторы ролей, а значениями - их строковые описания:

QHash<int, QByteArray> PersonModel::roleNames() const {
    QHash<int, QByteArray> roles;
    roles[NameRole] = "name";
    roles[AgeRole] = "age";
    return roles;
}

В файле main.qml определим ListView для вывода данных:

import QtQuick

Window {
    width: 250
    height: 150
    visible: true
    title: "METANIT.COM"

    ListView {
        objectName: "listView"
        anchors.fill: parent
        delegate: Text { text: name + " (" + age + ")" }
    }
}

И в файле main.cpp создадим модель и свяжем ее с ListView:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "personmodel.h"


int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;


    const QUrl url("qrc:/path/main.qml");

    engine.load(url);

    // создаем модель и определяем данные
    PersonModel model;
    model.addPerson(Person("Tom", 39));
    model.addPerson(Person("Bob", 43));
    model.addPerson(Person("Sam", 28));

    // устанавливаем модель для ListView
    QObject* listView = engine.rootObjects().first()->findChild<QObject*>("listView");
    listView->setProperty("model", QVariant::fromValue(&model));

    return app.exec();
}
Определение модели QAbstractItemModel в QML и Qt Quick

Установка модели для представления

В примере выше для установки модели мы находили элемент по имени (которое устанавливается свойством objectName) и и затем у найденного элемент устанавливали свойство model

QObject* listView = engine.rootObjects().first()->findChild<QObject*>("listView");
listView->setProperty("model", QVariant::fromValue(&model));

Также можно использовать другие способы. Например, передать модель через свойства контекста в main.cpp:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>  // для свойств контекста
#include "personmodel.h"


int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    const QUrl url("qrc:/path/main.qml");

    // создаем модель и определяем данные
    PersonModel model;
    model.addPerson(Person("Tom", 39));
    model.addPerson(Person("Bob", 43));
    model.addPerson(Person("Sam", 28));

    // передаем свойство контекста
    engine.rootContext()->setContextProperty("users", QVariant::fromValue(&model));
    // свойство контекста устанавливается перед load
    engine.load(url);

    return app.exec();
}

Здесь устанавливается свойство "users". В коде QML мы можем передать его значение модели ListView:

ListView {
    model: users
    anchors.fill: parent
    delegate: Text { text: name + " (" + age + ")" }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850