qml----Model/View入门(三)ListView分组显示

本文介绍如何使用 QML 的 ListView 实现数据分组展示,包括设置 section 属性、criteria 判断条件及 delegate 显示方式,并提供具体代码示例。

除了动画效果外,还有一个实用的功能就是按条件分组。如同手机里通讯录一般

section,就是实现分组的主角,简略讲讲这个主角的本领

section.property  表明了分组的依据,比如section.property: "cost"

section.criteria 指定了section,property的判断条件,通常有ViewSection.FullString和ViewSection.FirstCharacter两种,全串匹配和首字母匹配。匹配时不区分大小写

section.delegate 通过设置一个component,来显示每个section

还有很多section的属性,具体的可查帮助文档。不过有一点需要注意:listview的分组不会自动排序,也就是说,如果apple和huawei的手机交替出现时,那么listview则可能会显示多个           相同的section.

下面是个具体的实例

import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1

Rectangle {
    width: 360
    height: 400
    color: "#EEEEEE"

    Component{
        id: phoneModel

        ListModel{
            ListElement{
                name: "iphone 5"
                cost: "4900"
                manufacture: "Apple"
            }

            ListElement{
                name: "iphone 3gs"
                cost: "1000"
                manufacture: "Apple"
            }

            ListElement{
                name: "iphone 4"
                cost: "1800"
                manufacture: "Apple"
            }

            ListElement{
                name: "iphone 4s"
                cost: "2300"
                manufacture: "Apple"
            }

            ListElement{
                name: "B199"
                cost: "1590"
                manufacture: "HuaWei"
            }

            ListElement{
                name: "c88160"
                cost: "590"
                manufacture: "HuaWei"
            }

            ListElement{
                name: "galaxy s5"
                cost: "2300"
                manufacture: "sumsung"
            }

            ListElement{
                name: "galaxy s7"
                cost: "4900"
                manufacture: "sumsung"
            }

            ListElement{
                name: "galaxy s4"
                cost: "1200"
                manufacture: "sumsung"
            }

            ListElement{
                name: "MI5"
                cost: "2300"
                manufacture: "XiaoMi"
            }
        }
    }// phoneModel is end

    Component{
        id: phoneDelegate

        Item {
            id: wrapper
            width: parent.width
            height: 30
            ListView.onAdd: console.log("count:", ListView.view.count)

            MouseArea{
                anchors.fill: parent
                onClicked: wrapper.ListView.view.currentIndex = index
            }

            RowLayout{
                anchors.left: parent.left
                anchors.verticalCenter: parent.verticalCenter
                spacing: 8

                Text{
                    id: coll
                    text: name
                    color: wrapper.ListView.isCurrentItem ? "red" : "black"
                    font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18
                    Layout.preferredWidth: 120
                }

                Text{
                    text: cost
                    color: wrapper.ListView.isCurrentItem ? "red" : "black"
                    font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18
                    Layout.preferredWidth: 80
                }

                Text{
                    text: manufacture
                    color: wrapper.ListView.isCurrentItem ? "red" : "black"
                    font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18
                    Layout.fillWidth: true
                }
            }
        }
    }//phoneDelegate is end

    Component{
        id: sectionHeader

        Rectangle{
            width: parent.width
            height: childrenRect.height
            color: "lightsteelblue"
            Text{
                text: section
                font.bold: true
                font.pointSize: 20
            }
        }
    }//sectionHeader is end

    ListView{
        id: listView
        anchors.fill: parent
        delegate: phoneDelegate
        model: phoneModel.createObject(listView)
        focus: true
        highlight: Rectangle{
            color: "lightblue"
        }

        /*  特别注意的是,listview的分组不会引起listview自动按分组来处理Item的顺序。如果listView的Model
         *  内的数据没有按分组顺序编排,比如说samsung和apple的手机在model内交替出现,那么listview则可能会
         *  显示多个相同的section
         */
        section.property: "manufacture"
        section.criteria: ViewSection.FullString
        section.delegate: sectionHeader
    }
}

 

转载于:https://www.cnblogs.com/SaveDictator/p/8192623.html

<think> 我们正在讨论QtModel/View模式的数据交互原理及方式。根据之前的引用和讨论,我们可以总结如下: QtModel/View架构遵循MVC(Model-View-Controller)设计模式,但将Controller的功能并入View,因此主要分为Model(数据模型)和View(视图)两部分,而委托(Delegate)则负责控制数据的显示和编辑方式。 ### 数据交互原理 1. **模型(Model)**:负责管理数据,提供标准接口供视图和委托访问数据。所有模型都继承自`QAbstractItemModel`(或其子类,如`QAbstractListModel`、`QAbstractTableModel`)。 2. **视图(View)**:负责显示模型中的数据,并处理用户交互。视图通过模型索引(`QModelIndex`)来访问模型中的数据项。 3. **委托(Delegate)**:负责渲染数据项(在视图中绘制)和提供编辑控件(当用户编辑数据项时)。委托是连接视图和模型编辑操作的桥梁。 ### 数据交互方式 #### 1. 从模型到视图(数据显示- 视图通过调用模型的`data()`函数获取数据,该函数需要传入一个`QModelIndex`(模型索引)和一个角色(如`Qt::DisplayRole`用于显示文本,`Qt::EditRole`用于编辑)。 - 模型根据索引和角色返回相应的数据(以`QVariant`形式)。 - 视图在需要绘制某个数据项时,会调用委托的`paint()`方法(如果自定义了委托,可以重写此方法来自定义绘制),而委托在绘制时又会调用模型的`data()`来获取数据。 #### 2. 从视图到模型(数据修改) - 当用户通过视图编辑数据时,视图会调用委托的`createEditor()`创建编辑控件(如文本框、下拉框等)。 - 委托的`setEditorData()`方法被调用,将模型中的数据(通过`data(Qt::EditRole)`获取)设置到编辑控件中。 - 用户编辑完成后,委托的`setModelData()`方法被调用,将编辑控件中的数据提取出来,并通过调用模型的`setData()`函数将新数据写回模型。 - 模型在数据被修改后,会发出`dataChanged()`信号,该信号被视图接收,视图会更新显示。 #### 3. 模型索引(QModelIndex) - `QModelIndex`是模型数据项的索引,它包含行号、列号和一个指向父索引的指针(用于树形结构)。通过`QModelIndex`,我们可以定位到模型中的特定数据项。 - 模型索引由模型创建(通过`index()`函数),视图和委托使用模型索引来请求数据或修改数据。 #### 4. 信号与槽的通信 - 当模型的数据发生变化(如通过`setData()`修改了数据项)时,模型会发出`dataChanged()`信号,视图接收到这个信号后会更新相应的显示区域。 - 当模型的结构发生变化(如插入或删除行/列)时,模型会发出相应的信号(如`rowsInserted()`、`rowsRemoved()`等),视图会相应地调整其显示。 ### 示例:使用自定义模型和视图 以下是一个简单的示例,展示如何使用模型和视图进行数据交互: ```cpp // 创建模型(这里使用QStandardItemModel作为示例) QStandardItemModel model(4, 2); // 4行2列 // 填充数据 for (int row = 0; row < 4; ++row) { for (int col = 0; col < 2; ++col) { QModelIndex index = model.index(row, col, QModelIndex()); model.setData(index, QVariant(row * col)); } } // 创建视图并设置模型 QTableView tableView; tableView.setModel(&model); // 显示视图 tableView.show(); ``` ### 自定义委托的关键方法 在自定义委托时,为了确保数据同步,需要重写以下方法(如之前所述): - `createEditor()`:创建编辑控件。 - `setEditorData()`:将模型数据加载到编辑器(M→D)。 - `setModelData()`:将编辑后的数据保存回模型(D→M)。 - `updateEditorGeometry()`:调整编辑器在视图中的位置。 ### 注意事项 - 如果模型是代理模型(如`QSortFilterProxyModel`),在委托中需要将视图传递过来的索引映射到源模型(使用`mapToSource()`),然后再操作源模型。 - 模型在修改数据后必须发出`dataChanged()`信号,否则视图不会更新。 ### 引用说明 - 引用[1]提到,Qt提供了一些便利类(如`QListWidget`、`QTreeWidget`、`QTableWidget`),它们内部集成了模型和视图,使用方便但灵活性较低。对于复杂的数据管理,推荐使用Model/View架构[^1]。 - 引用[2]介绍了`QModelIndex`的结构和用途,它是模型和视图之间数据交互的桥梁[^2]。 - 引用[3]说明了Qt中模型类的层次结构,以及常用的模型类[^3]。 - 引用[4]展示了一个定时更新数据并通知视图的示例,通过发射`dataChanged()`信号来更新视图[^4]。 ### 总结 QtModel/View模式通过模型、视图和委托的分离开放,提供了灵活的数据管理方式。数据交互的核心在于模型索引(`QModelIndex`)和模型提供的接口(`data()`和`setData()`),以及委托在编辑过程中的中介作用。通过信号与槽机制,模型和视图之间实现了数据的同步更新。 ### 相关问题 1. 在自定义模型中如何实现`data()`和`setData()`方法? 2. 如何使用代理模型(如`QSortFilterProxyModel`)对数据进行排序和过滤? 3. 如何在树形结构(TreeView)中实现数据的动态加载? 4. 如何提高Model/View在大数据量下的性能? [^1]: QtModel/View。 许多便利类都源于标准的view类,它们方便了那些使用Qt中基于项的view与table类,它们不应该被子类化, 它们只是为Qt 3的等价类提供一个熟悉的接口。这些类有QListWidget,QTreeWidget,QTableWidget,它们提供了如Qt 3中的QListBox, QlistView,QTable相似的行为。这些类比View类缺少灵活性,不能用于任意的models,推介使用model/view的方法处理数据。 [^2]: qtmodel/view介绍。使用场景:常在含有用户界面的程序中使用该框架 框架分析之QModelIndex索引 model是用来存储数据的,view是用来显示数据的,二者是通过QModelIndex索引来建立联系。 class Q_CORE_EXPORT QModelIndex { friend class QAbstractItemModel; ... int r; // model中某个子数据项的行数(row) int c; // model中某个子数据项的列数(column) quintptr i; // model中某个父数据项的id(用于tree model) const QAbstractItemModel *m; // 记录模型,便于访问数据 inline QModelIndex parent() const; // 记录该父索引 }; [^3]: QtModel/view模式。所有的item models都基于QAbstractItemModel类,该类为item模型提供了一个抽象接口,定义了item模型一定能用于与model/view结构的其他组件进行交互操作的标准化接口;该类不支持被实例化,需要将其子类化来创建新模型。 如果需要一个模型用于项目视图,如QMLListView元素,或C++小部件QListViewQTableView,需要子类化QAbstractListModel或QAbstractTableModel,而不是该类。 另,QStringListModel用于存储简单的QString列表。 QStandardItemModel管理复杂的树形数据结构,每项可包含任意数据项。 QDirModel存储文件目录信息,以及QSqlQueryModel,QSqlTableModel、QSqlRelationTableModel用来访问管理数据库。 [^4]: 数据变化时通知视图类:数据发生改变→模型类发出dataChanged信号→视图类中的槽函数就会被调用 TreeModel::TreeModel() { ... timer = new QTimer(this); timer->setInterval(1000); connect(timer, SIGNAL(timeout()) , this, SLOT(timerHit())); timer->start(); } //槽函数 void TreeModel::timerHit() { numbers[14] = ( numbers[14] + 1 ) % 60; numbers[6] = numbers[14]+ numbers[13]; numbers[2] = numbers[6] + numbers[5]; numbers[0] = numbers[2] + numbers[1]; QModelIndex idx_14 = createIndex(1,0,14); QModelIndex idx_6 = createIndex(1,0,6); QModelIndex idx_2 = createIndex(1,0,2); quintptr id = 0; QModelIndex idx_0 = createIndex(0,0,id); emit dataChanged(idx_14, idx_14); emit dataChanged(idx_6, idx_6 ); emit dataChanged(idx_2, idx_2 ); emit dataChanged(idx_0, idx_0 ); } </think> ### Qt Model/View 模式数据交互原理及方式详解 QtModel/View 架构基于经典的 MVC(Model-View-Controller)设计模式,但将 Controller 功能并入 View 和 Delegate 中,形成以下核心组件: #### 1. **核心组件关系** $$ \begin{array}{ccc} \text{Model} & \xrightarrow{\text{数据存储}} & \text{View} \\ \uparrow \scriptsize{\texttt{委托}} & & \downarrow \scriptsize{\texttt{显示}} \\ \text{Delegate} & \xleftarrow{\text{用户交互}} & \text{User} \end{array} $$ #### 2. **数据交互原理** 1. **模型索引(QModelIndex)** - 核心数据结构:`(row, column, parent_id, model_ptr)`[^2] ```cpp QModelIndex index = model->index(row, col, parentIndex); QVariant data = model->data(index, Qt::DisplayRole); ``` - 实现模型与视图解耦:视图通过索引访问数据,无需知道存储细节 2. **数据流向控制** - **ModelView**: 视图通过`data()`方法请求数据,模型根据索引返回`QVariant` - **ViewModel**: 用户修改数据 → 委托调用`setModelData()` → 模型`setData()`更新存储 3. **变更通知机制** ```cpp // 模型数据变更后必须触发信号 void MyModel::updateData() { emit dataChanged(topLeftIndex, bottomRightIndex); // 关键通知[^4] } ``` - 视图自动连接`dataChanged`信号更新显示区域 - `rowsInserted/rowsRemoved`信号处理结构变化 #### 3. **标准交互接口** | 组件 | 关键方法 | 作用说明 | |-------------|------------------------------|------------------------------| | **Model** | `data(index, role)` | 提供显示/编辑数据 | | | `setData(index, value, role)`| 接收修改后的数据 | | **View** | `setModel(model)` | 绑定数据源 | | | `openPersistentEditor()` | 启动编辑模式 | | **Delegate**| `createEditor()` | 创建编辑控件 | | | `setEditorData()` | 加载模型数据到编辑器(M→V) | | | `setModelData()` | 保存编辑器数据到模型(V→M) | #### 4. **数据角色(Roles)机制** ```mermaid graph LR A[DisplayRole] -->|文本显示| B(View) C[EditRole] -->|编辑数据| D(Delegate) E[DecorationRole] -->|图标| F(View) G[ToolTipRole] -->|提示信息| H(View) ``` - 通过不同角色区分数据用途 - 自定义角色:`Qt::UserRole + N` #### 5. **代理模型(Proxy Model)交互** ```cpp // 创建代理链 QSortFilterProxyModel* proxy = new QSortFilterProxyModel; proxy->setSourceModel(sourceModel); // 连接原始模型 view->setModel(proxy); // 视图使用代理 // 委托中数据回写需转换索引 void MyDelegate::setModelData(...) { QModelIndex sourceIndex = proxy->mapToSource(proxyIndex); sourceModel->setData(sourceIndex, value); // 写入源模型[^3] } ``` #### 6. **完整交互流程示例** ```cpp // 1. 创建模型 QStandardItemModel model; model.setItem(0, 0, new QStandardItem("初始值")); // 2. 视图绑定模型 QTableView view; view.setModel(&model); // 3. 用户双击单元格触发编辑 // 4. 委托创建编辑器(QLineEdit) // 5. 委托调用setEditorData()加载"初始值" // 6. 用户修改为"新值"后按Enter // 7. 委托调用setModelData(): // model->setData(index, "新值", Qt::EditRole); // 8. 模型触发dataChanged信号 // 9. 视图自动更新显示 ``` #### 7. **性能优化策略** 1. **批处理更新** ```cpp model->blockSignals(true); // 暂停信号 // 批量修改数据... model->blockSignals(false); emit dataChanged(startIdx, endIdx); // 单次通知 ``` 2. **懒加载数据** 重写`fetchMore()`实现按需加载大数据集 3. **视图优化** ```cpp view->setUniformRowHeights(true); // 加速滚动 view->setViewportMargins(0,0,0,0); // 减少绘制区域 ``` ### 设计原则总结 1. **关注点分离** 模型专注数据存储,视图专注显示,委托专注交互 2. **统一接口** 所有模型继承`QAbstractItemModel`,保证接口一致性[^3] 3. **信号驱动** 通过信号通知变更,避免主动轮询 4. **索引解耦** `QModelIndex`隔离物理存储和逻辑访问 ### 相关问题 1. 如何在自定义模型中正确实现`data()`和`setData()`方法? 2. 使用代理模型时数据同步有哪些特殊注意事项? 3. 如何通过委托实现复杂数据验证? 4. 模型/视图架构相比便利类(如`QTableWidget`)有哪些性能优势?[^1] [^1]: QtModel/View。 许多便利类都源于标准的view类,它们方便了那些使用Qt中基于项的view与table类,它们不应该被子类化, 它们只是为Qt 3的等价类提供一个熟悉的接口。这些类有QListWidget,QTreeWidget,QTableWidget,它们提供了如Qt 3中的QListBox, QlistView,QTable相似的行为。这些类比View类缺少灵活性,不能用于任意的models,推介使用model/view的方法处理数据。 [^2]: qtmodel/view介绍。使用场景:常在含有用户界面的程序中使用该框架 框架分析之QModelIndex索引 model是用来存储数据的,view是用来显示数据的,二者是通过QModelIndex索引来建立联系。 class Q_CORE_EXPORT QModelIndex { friend class QAbstractItemModel; ... int r; // model中某个子数据项的行数(row) int c; // model中某个子数据项的列数(column) quintptr i; // model中某个父数据项的id(用于tree model) const QAbstractItemModel *m; // 记录模型,便于访问数据 inline QModelIndex parent() const; // 记录该父索引 }; [^3]: QtModel/view模式。所有的item models都基于QAbstractItemModel类,该类为item模型提供了一个抽象接口,定义了item模型一定能用于与model/view结构的其他组件进行交互操作的标准化接口;该类不支持被实例化,需要将其子类化来创建新模型。 如果需要一个模型用于项目视图,如QMLListView元素,或C++小部件QListViewQTableView,需要子类化QAbstractListModel或QAbstractTableModel,而不是该类。 另,QStringListModel用于存储简单的QString列表。 QStandardItemModel管理复杂的树形数据结构,每项可包含任意数据项。 QDirModel存储文件目录信息,以及QSqlQueryModel,QSqlTableModel、QSqlRelationTableModel用来访问管理数据库。 [^4]: 数据变化时通知视图类:数据发生改变→模型类发出dataChanged信号→视图类中的槽函数就会被调用 TreeModel::TreeModel() { ... timer = new QTimer(this); timer->setInterval(1000); connect(timer, SIGNAL(timeout()) , this, SLOT(timerHit())); timer->start(); } //槽函数 void TreeModel::timerHit() { ... emit dataChanged(idx_14, idx_14); ...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值