qml----Model/View入门(二)ListView动画效果

在上一节中,我们实现了listview的基本功能以及对数据的操作,这节我们来讲如何添加动画效果

代码如下,效果直接运行即可看到

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: "iphone5"
                cost: "4900"
                manufacture: "Apple"
            }

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

            ListElement{
                name: "MI 2S"
                cost: "1900"
                manufacture: "xiaomi"
            }

            ListElement{
                name: "galaxy s6"
                cost: "4900"
                manufacture: "samsung"
            }
        }
    }// iphoneModel 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.pixelSize: 20
                    font.bold: true
                    Layout.preferredWidth: 120
                }

                Text{
                    text: "Manufacture"
                    font.pixelSize: 20
                    font.bold: true
                    Layout.fillWidth: true
                }
            }
        }
    }// headView is end

    Component{
        id: footerView

        Item {
            id: footerRootItem
            width: parent.width
            height: 30
            signal add()
            signal insert()

            Button{
                id: addOne
                anchors.right: parent.right
                anchors.rightMargin: 4
                anchors.verticalCenter: parent.verticalCenter
                text: "Add"
                onClicked: footerRootItem.add()
            }

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

    Component{
        id: phoneDelegate

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

            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
                }
            }

            MouseArea{
                anchors.fill: parent

                onClicked: {
                    wrapper.ListView.view.currentIndex = index
                    mouse.accepted = true
                }

                onDoubleClicked: {
                    wrapper.ListView.view.model.remove(index)
                    mouse.accepted = true
                }
            }
        }
    }//phoneDelegate is end

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

        /*  populate
         *  populate指定一个过度动画,在listView第一次实例化或者因为Model变化而需要创建Item时应用
         *  下面就是产生一个渐变效果
         */
        populate: Transition{
            NumberAnimation{
                property: "opacity"
                from: 0
                to: 1.0
                duration: 1000
            }
        }//populate Transition is end

        add:Transition {
            ParallelAnimation{
                NumberAnimation{
                    property: "opacity"
                    from: 0
                    to : 1.0
                    duration: 1000
                }

                NumberAnimation{
                    property: "y"
                    from: 0
                    duration:  1000
                }
            }
        }// add transition is end

        /*  displaced属性
         *  此属性用于指定通用的、由于model变化导致Item位移时的动画效果,还有removeDisplaced、moveDisplaced
         *  如果同时指定了displaced 和xxxDisplaced,那么xxxDisplaced生效
         */
        displaced: Transition {
            SpringAnimation{
                property: "y"
                spring: 3
                damping: 0.1
                epsilon: 0.25
            }
        }

        remove: Transition {
            SequentialAnimation{
                NumberAnimation{
                    property: "y"
                    to: 0
                    duration: 600
                }

                NumberAnimation{
                    property: "opacity"
                    to:0
                    duration: 600
                }
            }
        }//remove Transition is end


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

 

转载于:https://www.cnblogs.com/SaveDictator/p/8192517.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); ...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值