qml----Model/View入门(一)listview

本文详细介绍了QML中的Model-View-Delegate模式,并通过一个具体的例子展示了如何利用这种模式进行数据展示和操作。包括数据模型的定义、视图的布局设置以及委托组件的使用。

在SmallTalk中有一个经典的设计模式-MVC。即模型-视图-控制器,在qml中将control改成了delegate(委托),也就是现在的Model-View-Delegate.换了个说法,Model还是负责数据,View管着视图输出,Delegate呢就是一个介于视图和数据之间的桥梁。

下面先来看个例子,效果图如下:

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

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

    Component{
        id: phoneModel

        ListModel{
            ListElement{
                name: "iphone 3GS"
                cost: "1000"
                manufacturer: "Apple"
            }

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

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

            ListElement{
                name: "iphne 5"
                cost: "1590"
                manufacturer: "Apple"
            }

            ListElement{
                name: "iphone B199"
                cost: "1590"
                manufacturer: "HuaWei"
            }

            ListElement{
                name: "MI 25"
                cost: "1999"
                manufacturer: "XiaoMi"
            }

            ListElement{
                name: "GALAXY s5"
                cost: "4698"
                manufacturer: "Samsung"
            }
        }
    }//phoneModel is end(视图数据)

    Component{
        id: headView
        Item {
            width: parent.width
            height: 30
            RowLayout{
                anchors.left: parent.left
                anchors.verticalCenter: parent.verticalCenter
                spacing: 8
                Text{
                    text: "name"
                    font.bold: true
                    font.pixelSize: 20
                    Layout.preferredWidth: 120
                }

                Text{
                    text: "cost"
                    font.bold: true
                    font.pixelSize: 20
                    Layout.preferredWidth: 80
                }

                Text{
                    text: "manufacturer"
                    font.bold: true
                    font.pixelSize: 20
                    Layout.fillWidth: true
                }
            }
        }
    }//headview is end(定义的表头)

    Component{
        id: footerView

        Item{
            id: footerRootItem
            width: parent.width
            height: 30
            property alias text: txt.text
            signal clean()
            signal change()
            signal replace()

            Text{
                id: txt
                anchors.left: parent.left
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: parent.width/4
                font.italic: true
                color: "blue"
                verticalAlignment: Text.AlignVCenter
            }

            Button{
                id: cleanBtn
                anchors.right: parent.right
                anchors.verticalCenter: parent.verticalCenter
                text: "clear"
                onClicked: footerRootItem.clean()
            }//清空视图数据的按钮

            Button{
                id: changeBtn
                anchors.right: cleanBtn.left
                anchors.rightMargin:  5
                anchors.verticalCenter: parent.verticalCenter
                text: "change data"
                onClicked: footerRootItem.change()
            }//改变选中行数据

            Button{
                id: replaceBtn
                anchors.right: changeBtn.left
                anchors.rightMargin: 5
                anchors.verticalCenter: parent.verticalCenter
                text: "replace data"
                onClicked: footerRootItem.replace()
            }//替换选中行的数据
        }

    }//footer属性允许我们制定listview的页脚,footerItem保存了footer组件创建出来的item对象,这个item会被
    //添加到listview的末尾,在所有可见的item之后

    Component{
        id: phoneDelegate
        Item {
            id: wrapper
            width: parent.width
            height: 30

            MouseArea{
                anchors.fill: parent
                onClicked: wrapper.ListView.view.currentIndex = index
                onDoubleClicked: wrapper.ListView.view.model.remove(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
                }//coll text is end

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

                Text{
                    text: manufacturer
                    color: wrapper.ListView.isCurrentItem ? "red" : "black"
                    font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18
                    Layout.fillWidth: true
                }//manufacturer is end
            }
        }
    }//delegate is end(视图的委托)

    ListView{
        id: listView
        anchors.fill: parent
        focus: trues
        delegate: phoneDelegate
        model: phoneModel.createObject(listView)
        header: headView
        footer: footerView
        highlight: Rectangle{
            color: "lightblue"
        }

        onCurrentIndexChanged: {
            if(listView.currentIndex >= 0){
                var data = listView.model.get(listView.currentIndex)
                listView.footerItem.text = data.name + "," + data.cost + "," + data.manufacturer
            }
            else
                listView.footerItem.text = " "
        }

        function changeItem()//修改模型数据
        {
            listView.model.setProperty(listView.currentIndex, "manufacturer", "China")
        }

        function replaceItem()//替换模型数据
        {
            listView.model.set(listView.currentIndex, {"name": "234 ninbi",                                                    "cost": 1999, "manufacturer": "ZhongXing"})
        }

        Component.onCompleted: {
            listView.footerItem.clean.connect(listView.model.clear)//关联信号
            listView.footerItem.change.connect(listView.changeItem)//关联改变信号
            listView.footerItem.replace.connect(listView.replaceItem)
        }
    }//定义listview
}
View Code
这个例子比较简单,增加了头和footer。另外,在下面放了几个按钮,用以演示对数据的修改和删除。如果要实现插入数据,那么需要在footerView中添加signal insert()

 ,并添加Button

Button{         id: insertOne
                anchors.right: addOne.left
                anchors.leftMargin: 4
                anchors.verticalCenter: parent.verticalCenter
                text: "Insert"
                onClicked: footerRootItem.insert()
            }

并且在 listView中实现Insert函数、建立与footerView的信号链接

 function addOne()
        {
            model.append(
                {
                    "name": "MX5",
                    "cost": "1899",
                    "manufacture" : "MeiZu"
                }
            )
        }

        function insertOne()
        {
            model.insert(Math.round(Math.random() * model.count),
               {
                    "name" : "HTC One E8",
                    "cost" : "2900",
                    "manufacture" : "HTC"
               }
            )
        }

        Component.onCompleted: {
            listView.footerItem.add.connect(listView.addOne)
            listView.footerItem.insert.connect(listView.insertOne)
        }

注意虽然delegate有很多附加属性以及信号等,但是只有顶层的item才能直接使用这些属性,
非顶层的item需要通过顶层item的id来访问这些属性

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

<think> 我们正在讨论Qt的Model/View模式的数据交互原理及方式。根据之前的引用和讨论,我们可以总结如下: Qt的Model/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]。 ### 总结 Qt的Model/View模式通过模型、视图和委托的分离开放,提供了灵活的数据管理方式。数据交互的核心在于模型索引(`QModelIndex`)和模型提供的接口(`data()`和`setData()`),以及委托在编辑过程中的中介作用。通过信号与槽机制,模型和视图之间实现了数据的同步更新。 ### 相关问题 1. 在自定义模型中如何实现`data()`和`setData()`方法? 2. 如何使用代理模型(如`QSortFilterProxyModel`)对数据进行排序和过滤? 3. 如何在树形结构(TreeView)中实现数据的动态加载? 4. 如何提高Model/View在大数据量下的性能? [^1]: Qt 的Model/View。 许多便利类都源于标准的view类,它们方便了那些使用Qt中基于项的view与table类,它们不应该被子类化, 它们只是为Qt 3的等价类提供个熟悉的接口。这些类有QListWidget,QTreeWidget,QTableWidget,它们提供了如Qt 3中的QListBox, QlistView,QTable相似的行为。这些类比View类缺少灵活性,不能用于任意的models,推介使用model/view的方法处理数据。 [^2]: qt中model/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]: Qt的Model/view模式。所有的item models都基于QAbstractItemModel类,该类为item模型提供了个抽象接口,定义了item模型定能用于与model/view结构的其他组件进行交互操作的标准化接口;该类不支持被实例化,需要将其子类化来创建新模型。 如果需要个模型用于项目视图,如QMLListView元素,或C++小部件QListView或QTableView,需要子类化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 模式数据交互原理及方式详解 Qt 的 Model/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]: Qt 的Model/View。 许多便利类都源于标准的view类,它们方便了那些使用Qt中基于项的view与table类,它们不应该被子类化, 它们只是为Qt 3的等价类提供个熟悉的接口。这些类有QListWidget,QTreeWidget,QTableWidget,它们提供了如Qt 3中的QListBox, QlistView,QTable相似的行为。这些类比View类缺少灵活性,不能用于任意的models,推介使用model/view的方法处理数据。 [^2]: qt中model/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]: Qt的Model/view模式。所有的item models都基于QAbstractItemModel类,该类为item模型提供了个抽象接口,定义了item模型定能用于与model/view结构的其他组件进行交互操作的标准化接口;该类不支持被实例化,需要将其子类化来创建新模型。 如果需要个模型用于项目视图,如QMLListView元素,或C++小部件QListView或QTableView,需要子类化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); ...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值