八、Qt Quick进阶:GridView、TableView、TreeView与自定义模型
承接ListView基础,深入讲解Qt Quick三大视图组件及JS/C++自定义模型实现,助力构建复杂高效的结构化数据UI。
八、Qt Quick进阶:GridView、TableView、TreeView与自定义模型-MakerLi

第8章:模型与视图(二)——GridView、TableView、TreeView、自定义模型(C++/JavaScript)


本章概述

上一章我们掌握了ListView和基础模型的用法,这一章将解锁Qt Quick中更强大的视图组件:GridView网格视图、TableView表格视图和TreeView树形视图,同时学会用JavaScript或C++打造自定义模型,实现更灵活的数据管理。掌握这些技能,复杂高效的结构化数据UI就能轻松构建!






GridView(网格视图)

GridView像一个规整的格子架,特别适合展示图片库、图标集这类需要整齐排列的内容。它继承自Flickable,天生支持滚动和轻拂操作,浏览体验顺畅。


核心属性需重点掌握:

  • cellWidth/cellHeight:定义每个网格单元的宽高,决定格子尺寸;
  • flow:控制布局流向,可选从左到右(GridView.FlowLeftToRight)或从上到下(GridView.FlowTopToBottom);
  • layoutDirection:设置整体布局方向,支持左到右或右到左;
  • snapMode:开启对齐模式,让项目精准贴合网格,避免错位。

以下是一个水果网格示例,直观展示用法:

import QtQuick 2.15
import QtQuick.Controls 2.15

GridView {
    width: 400; height: 300
    cellWidth: 100; cellHeight: 100

    model: ListModel {
        ListElement { name: "苹果"; color: "red" }
        ListElement { name: "香蕉"; color: "yellow" }
        ListElement { name: "葡萄"; color: "purple" }
        // 可继续添加更多数据
    }

    delegate: Rectangle {
        width: GridView.view.cellWidth - 5
        height: GridView.view.cellHeight - 5
        color: model.color
        border.color: "gray"
        radius: 8

        Text {
            anchors.centerIn: parent
            text: model.name
            font.bold: true
        }
    }
}






TableView(表格视图)

TableView专门用于展示二维表格数据,比如报表、统计数据等。它需要配合TableModel或自定义模型使用,还能精细控制行、列标题和单元格的可视化样式。


几个关键概念要理清:

  • 角色(Roles):模型中每个数据项可包含多个角色,如用于显示的display、用于编辑的edit,方便不同场景调用数据;
  • 委托(Delegate):可分别为行、列、单元格定义专属样式,让表格更个性化;
  • 选择(Selection):通过附加属性TableView.selected就能轻松管理单元格的选中状态。

很多人会混淆TableView与其他视图,这里帮你区分:

  • TableView vs ListView:ListView是一维列表,TableView是二维表格;TableView需要更结构化的模型,对大数据集的性能优化更出色;
  • TableView vs GridView:TableView有明确的行列标题,单元格是行与列的交集;GridView的每个项目独立,更适合展示集合类内容,而TableView主打数据表格展示。




TreeView(树形视图)

TreeView是展示层次化数据的神器,比如文件系统目录、公司组织架构这类有父子层级的数据。但它对模型有特殊要求,必须实现几个核心接口:

  • rowCount(parent):返回指定父节点下的子节点数量;
  • index(row, column, parent):创建子节点的索引;
  • parent(index):返回指定索引节点的父索引;
  • hasChildren(parent):判断指定父节点是否包含子节点;
  • data(index, role):返回指定索引和角色对应的数据。

TreeView的架构清晰:TreeView负责可视化展示,TreeModel提供层次化数据,Node Delegate定义每个节点的外观,三者配合就能完美呈现树形结构。






自定义模型

当ListModel、XmlListModel等内置模型满足不了需求时,就需要创建自定义模型,这里分JavaScript和C++两种实现方式:


JavaScript自定义模型

通过继承QtObject并实现getcount等必要接口即可,简单易上手,适合快速原型开发。


以下是一个可管理数据数组的JS模型示例,还能通知视图数据变化:

// MyJsModel.qml
import QtQuick 2.15

QtObject {
    id: root

    property var dataArray: []

    function append(item) {
        dataArray.push(item);
        // 发送信号通知视图更新
        root.dataChanged();
    }

    function get(index) {
        return dataArray[index];
    }

    function count() {
        return dataArray.length;
    }

    signal dataChanged()
}

使用时直接在ListView中引用并初始化数据:

ListView {
    model: MyJsModel {
        Component.onCompleted: {
            for(var i=0; i<10; i++) append({value: i*10})
        }
    }
    delegate: Text { text: modelData.value }
}


C++自定义模型

如果追求高性能和强大功能,建议用C++实现,通常继承QAbstractItemModel或其子类(如QAbstractListModel)。


以下是一个简单的列表模型示例:

// mylistmodel.h
#include <QAbstractListModel>
#include <QVector>

class MyListModel : public QAbstractListModel
{
    Q_OBJECT
public:
    enum Roles { ValueRole = Qt::UserRole + 1 };

    explicit MyListModel(QObject *parent = nullptr);

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    QHash<int, QByteArray> roleNames() const override;

    void addValue(int value);

private:
    QVector<int> m_data;
};
// mylistmodel.cpp
int MyListModel::rowCount(const QModelIndex &parent) const {
    // 父节点有效则为子节点,返回0;否则返回数据总数
    return parent.isValid() ? 0 : m_data.size();
}

QVariant MyListModel::data(const QModelIndex &index, int role) const {
    if (!index.isValid() || index.row() >= m_data.size())
        return QVariant();

    if (role == ValueRole)
        return m_data.at(index.row());

    return QVariant();
}

QHash<int, QByteArray> MyListModel::roleNames() const {
    // 映射角色名,方便QML调用
    return { {ValueRole, "value"} };
}

void MyListModel::addValue(int value) {
    // 通知视图即将插入行,确保UI同步
    beginInsertRows(QModelIndex(), m_data.size(), m_data.size());
    m_data.append(value);
    endInsertRows();
}

要在QML中使用该模型,需先在C++主函数中注册:

qmlRegisterType<MyListModel>("CustomModels", 1, 0, "MyListModel");

之后在QML中导入CustomModels模块即可使用。






总结与最佳实践

  1. 选对视图:根据数据结构和展示需求选择:网格内容用GridView,表格数据用TableView,层级数据用TreeView;
  2. 模型驱动视图:严格遵循模型-视图-委托(MVD)架构,分离数据与UI,提升代码可维护性;
  3. 性能优化
  4. 大数据集优先使用C++模型,性能远优于JavaScript模型;
  5. 委托中避免复杂计算和过多子组件,减少渲染压力;
  6. 合理设置cacheBuffer属性,预加载部分项目,提升滚动流畅度;
  7. 自定义模型选型:复杂数据操作、网络数据绑定或高性能需求优先选C++模型;简单场景用JavaScript模型即可。

数据流转逻辑清晰:数据源(数据库、文件、网络等)→ 模型(ListModel、TableModel、自定义模型)→ 视图(ListView、GridView、TableView),确保数据与UI高效联动。