使用C++模型与Qt Quick视图

在自定义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) }});

此示例的完整源代码位于Qt安装目录内的examples/quick/models/stringlistmodel/examples/quick/models/stringlistmodel中。

注意:视图无法知道QStringList的内容已更改。如果QStringList发生更改,则必须通过再次设置视图的model属性来重置模型。

基于QVariantList的模型

模型可能是一个单独的QVariantList,它通过modelData角色提供列表内容。

API与QStringList的工作方式相同,如前所述。

注意:视图无法知道QVariantList的内容已更改。如果QVariantList发生更改,则必须重置模型。

基于QObjectList的模型

QObject*值列表也可用作模型。QList将列表中的对象属性作为roles提供。

以下应用程序创建了一个具有Q_PROPERTY值的DataObject类,当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);

    const QStringList colorList = {"red",
                                   "green",
                                   "blue",
                                   "yellow"};

    const QStringList moduleList = {"Core", "GUI", "Multimedia", "Multimedia Widgets", "Network",
                                    "QML", "Quick", "Quick Controls", "Quick Dialogs",
                                    "Quick Layouts", "Quick Test", "SQL", "Widgets", "3D",
                                    "Android Extras", "Bluetooth", "Concurrent", "D-Bus",
                                    "Gamepad", "Graphical Effects", "Help", "Image Formats",
                                    "Location", "Mac Extras", "NFC", "OpenGL", "Platform Headers",
                                    "Positioning", "Print Support", "Purchasing", "Quick Extras",
                                    "Quick Timeline", "Quick Widgets", "Remote Objects", "Script",
                                    "SCXML", "Script Tools", "Sensors", "Serial Bus",
                                    "Serial Port", "Speech", "SVG", "UI Tools", "WebEngine",
                                    "WebSockets", "WebView", "Windows Extras", "XML",
                                    "XML Patterns", "Charts", "Network Authorization",
                                    "Virtual Keyboard", "Quick 3D", "Quick WebGL"};

    QList<QObject *> dataList;
    for (const QString &module : moduleList)
        dataList.append(new DataObject("Qt " + module, colorList.at(rand() % colorList.length())));

    QQuickView view;
    view.setResizeMode(QQuickView::SizeRootObjectToView);
    view.setInitialProperties({{ "model", QVariant::fromValue(dataList) }});
    ...

QObject*可以通过modelData属性获取。为了方便起见,对象属性也可以直接在代理的上下文中获取。在这里,view.qml引用了ListView代理中的DataModel属性。

ListView {
    id: listview
    width: 200; height: 320
    required model
    ScrollBar.vertical: ScrollBar { }

    delegate: Rectangle {
        width: listview.width; height: 25

        required color
        required property string name

        Text { text: parent.name }
    }
}

注意到了对颜色属性的运用。您可以通过在派生类型中声明为required来要求现有的属性。

此示例的完整源代码可在Qt安装目录中的examples/quick/models/objectlistmodel获取。

注意:视图无法知道QList的内容是否发生变化。如果QList发生变化,则需要通过再次设置model属性来重置模型。

QAbstractItemModel 子类

可以通过派生QAbstractItemModel来定义一个模型。如果您有一个比其他方法更复杂的模型,这可能是最好的方法。一个QAbstractItemModel还可以在模型数据发生变化时自动通知QML视图。

可以通过重写QAbstractItemModel::roleNames()来将QAbstractItemModel子类的角色公开给QML。

这是一个具有名为AnimalModelQAbstractListModel子类的应用程序,该类公开了typesize角色。它重写了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 = nullptr);
    ...
};

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)}});
    ...

这个模型通过访问typesize角色的ListView代理来显示。

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等函数时通知视图,以表示模型已更改。有关更多信息,请参阅模型派生类参考

此示例的完整源代码可在Qt安装目录中的examples/quick/models/abstractitemmodel获取。

QAbstractItemModel提供了一张表的层次结构,但当前由QML提供的视图只能显示列表数据。为了显示分层模型的子列表,请使用提供以下属性和函数以用于QAbstractItemModel类型列表模型的DelegateModel QML类型。

SQL 模型

Qt 提供支持 SQL 数据模型的 C++ 类。这些类在底层的 SQL 数据上透明工作,减少了运行 SQL 查询进行基本 SQL 操作(如创建、插入或更新)的需要。有关这些类的更多详细信息,请参阅使用 SQL 模型类

尽管 C++ 类提供了操作 SQL 数据的完整功能集,但它们不提供对 QML 的数据访问。因此,您必须将这些类之一作为子类实现 C++ 自定义数据模型,并将其作为类型或上下文属性 expose 到 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 数据库中的数据。示例 chat tutorial 通过实现一个从 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

上述示例使用视图上的必需属性在 QML 组件中直接设置模型值。此方法的替代方法是注册 C++ 模型类作为 QML 类型(参见 从 C++ 定义 QML 类型)。这允许在 QML 中直接创建模型类作为类型

C++
class MyModel : public QAbstractItemModel
{
    Q_OBJECT
    QML_ELEMENT

    // [...]
}
QML
MyModel {
    id: myModel
}
ListView {
    width: 200; height: 250
    model: myModel
    delegate: Text {
        required property string someProperty
        text: someProperty
    }
}

有关在 C++ 中编写 QML 类型的详细信息,请参阅使用 C++ 编写 QML 扩展

更改模型数据

除了 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++ 项视图(如 QListViewQTableView)不同,必须从 QML 委托中显式调用 setData() 方法,以便在适当的时候执行。这是通过简单地将新值分配给相应的模型属性来完成的。

ListView {
    anchors.fill: parent
    model: EditableModel {}
    delegate: TextField {
        width: ListView.view.width
        text: model.edit
        onAccepted: model.edit = text
    }
}

注意: edit 角色等于 Qt::EditRole。有关内置角色名称,请参阅 roleNames()。然而,现实生活中的模型通常会注册自定义角色。

© 2024 Qt公司有限公司。本文件中包含的文档贡献的版权属于其各自的所有者。提供的文档根据自由软件基金会公布的《GNU自由文档许可》第1.3版进行许可。