一个QMLListView的例子--
一般人不知道怎么去过滤ListView里面的数据,下面是一个转载的文章:http://imaginativethinking.ca/use-qt-quicks-delegatemodelgroup/
How to use Qt Quick’s DelegateModelGroup
By: Brad van der Laan, C.Tech.
Jul
07
2014In this tutorial I’m going to show you how to use Qt Quick’s DelegateModelGroup to re-organize a list of data in QML.
In QtQuick you have a View and a Data Model where the Data Model holds the data that the View will present to the user. Your Data Model may hold data that needs to be presented in a number of different ways. The different presentation views may only need to show a sub set of that data; In order to achieve this we need to present each View with a sub-set of the contents of the Data Model. We can do this via a Proxy Model such as the DelegateModel with the use of a DelegateModelGroup.
In QML there is a component called a DelegateModel which is essentially a proxy model allowing you to sort or filter the contents of the Data Model before passing it along to the View. The DelegateModel has a property called rootIndex which you can use in order to pick which branch of the Data Model (assuming its a Tree) to use in the View. There is also a component called a DelegateModelGroup which can be used by a DelegateModel to apply filtering and/or sorting to the data being retrieved from the Data Model.
Lets get started with some code shall we.
First create a new Qt Quick UI project (*.qmlproject), remember this is a type of project that has only QML (no C++). Call your new project DelegateModel_Sample1.
In the DelegateModel_Sample1.qml file that gets auto generated per the Qt Quick UI project template add the following ListModel to act as our Data Model.
view plaincopy to clipboardprint?
ListModel {
id: myModel
ListElement { role_display: "One"; role_value: 0; }
ListElement { role_display: "One"; role_value: 2; }
ListElement { role_display: "One"; role_value: 3; }
ListElement { role_display: "One"; role_value: 4; }
ListElement { role_details: "Two"; role_value: 5; }
ListElement { role_details: "Three"; role_value: 6; }
ListElement { role_details: "Four"; role_value: 7; }
ListElement { role_details: "Five"; role_value: 8; }
ListElement { role_details: "Six"; role_value: 9; }
ListElement { role_keyID: "Seven"; role_value: 10; }
ListElement { role_keyID: "Eight"; role_value: 11; }
ListElement { role_keyID: "hello"; role_value: 12; }
}
ListModel {
id: myModel
ListElement { role_display: "One"; role_value: 0; }
ListElement { role_display: "One"; role_value: 2; }
ListElement { role_display: "One"; role_value: 3; }
ListElement { role_display: "One"; role_value: 4; }
ListElement { role_details: "Two"; role_value: 5; }
ListElement { role_details: "Three"; role_value: 6; }
ListElement { role_details: "Four"; role_value: 7; }
ListElement { role_details: "Five"; role_value: 8; }
ListElement { role_details: "Six"; role_value: 9; }
ListElement { role_keyID: "Seven"; role_value: 10; }
ListElement { role_keyID: "Eight"; role_value: 11; }
ListElement { role_keyID: "hello"; role_value: 12; }
}
Notice that I’ve named the roles role_ I use this convention so as to make it clear in the QML that I’m referring to a role identifier vs. an id of another component. In practice I’ve run into issues where a component in QML was given the same id as a defined role. This causes conflicts and confusion as its not clear what was being referred to, a QML Component or data from a model. This is typically more of an issue when your model is in C++ as the role identifier isn’t defined in the QML script. To solve this I personally adopted a naming convention of prefixing role identifiers with role_ to make them unique and easily identified in the QML script.
Now we have a Data Model that has twelve (12) entries. Lets say we want to present only the entries that have the role_display role data. To do this we need to filter out all the other entries so that we’re left with only the four (4) entries who have the appropriate data. To do this we’re going to use a DelegateModel to act as our Proxy Model.
DelegateModel{
id: displayDelegateModel
delegate: displayDelegate
model: myModel
}
DelegateModel{
id: displayDelegateModel
delegate: displayDelegate
model: myModel
}
Note: In order to use the DelegateModel you need to import the QtQml.Models library by adding the following statement to your QML script.
view plaincopy to clipboardprint?
import QtQml.Models 2.1
import QtQml.Models 2.1
Here we told the displayDelegateModel that its root model (or Data Model) is the myModel we created above and we’ve also told it how to represent the data on screen via its delegate property (we’ll defined displayDelegate later on as a Component).
The DelegateModel component wraps up a model and a delegate to be used by a view. By simply telling a view to use this component it will know to use the delegate defined in the DelegateModel vs. having to set its delegate property. The DelegateModel replaces the older Qt Quick element VisualDataModel.
If we plug this into a View as is we’ll get all 12 entries as we’ve not actually performed any sort of filtering on the data in the model.
Enters the DelegateModelGroup
In order to actually filter the data before it gets to the View, so that it only presents a sub set of the data, we need to add a DelegateModelGroup to the DelegateModel component.
view plaincopy to clipboardprint?
DelegateModel {
id: displayDelegateModel
delegate: displayDelegate
model: myModel
groups: [
DelegateModelGroup {
includeByDefault: false
name: "displayField"
}
]
filterOnGroup: "displayField"
}
DelegateModel {
id: displayDelegateModel
delegate: displayDelegate
model: myModel
groups: [
DelegateModelGroup {
includeByDefault: false
name: "displayField"
}
]
filterOnGroup: "displayField"
}
Here we add a DelegateModelGroup and give the group a distinctive name displayField. By setting the includeByDefault property to false we are telling the DelegateModelGroup not to automatically include all the entries in the myModel Data Model into the displayField group. If we left it at this point the displayField group would be empty as all 12 entries from the myModel would be in the un-grouped section.
Now that we have a group that we can use we then tell the DelegateModel (displayDelegateModel) to filter its data on the displayField group via the filterOnGroup property. That is we are telling the DelegateModel to only show entries that are part of the displayField group and filter out all entries which are not a member of this group. If we were to plug the displayDelegateModel into a view now we would get zero (0) entries shown in the view. That is because at this point no entries in the myModel Data Model are members of the displayField group since we told the DelegateModelGroup not to auto include the entries.
What we need to do now is start adding entries to the displayField group. I suppose we could do that in a number of different places; we could even do it dynamically via user input so that we could use a single view to show different sub sets of data per the user input. For this example lets just do it once during the Component.onCompleted event handler; that is once the DelegateModel component has finished being initialized.
view plaincopy to clipboardprint?
DelegateModel {
id: displayDelegateModel
delegate: displayDelegate
model: myModel
groups: [
DelegateModelGropu {
includeByDefault: false
name: "displayField"
}
]
filterOnGroup: "displayField"
Component.onCompleted: {
var rowCount = myModel.count;
items.remove(0,rowCount);
for( var i = 0;i < rowCount;i++ ) {
var entry = myModel.get(i);
if(entry.role_display !== undefined) {
items.insert(entry, "displayField");
}
}
}
}
DelegateModel {
id: displayDelegateModel
delegate: displayDelegate
model: myModel
groups: [
DelegateModelGropu {
includeByDefault: false
name: "displayField"
}
]
filterOnGroup: "displayField"
Component.onCompleted: {
var rowCount = myModel.count;
items.remove(0,rowCount);
for( var i = 0;i < rowCount;i++ ) {
var entry = myModel.get(i);
if(entry.role_display !== undefined) {
items.insert(entry, "displayField");
}
}
}
}
Here using properties and functions of the ListModel I am looping through all twelve (12) entries that exist in myModel and for each entry I am checking to see if it contains the role_display Role. If it does I use the items property of the DelegateModel, which is a reference to its default group and since we only have one its the DelegateModelGroup that we setup called displayField, and add the given entry to said group. Now at the completion of the loop the displayField group will contain only the four (4) entries from myModel which have the role_display Role defined.
When I plug this into a View I'll see these four (4) entries displayed on the screen.
Here is the complete code example:
view plaincopy to clipboardprint?
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("DelegateModel Sample 1")
width: 640
height: 600
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
Item {
id: root
anchors.fill: parent
readonly property int rowHeight: 25
ListModel {
id: myModel
ListElement { role_display: "One"; role_value: 0; }
ListElement { role_display: "One"; role_value: 2; }
ListElement { role_display: "One"; role_value: 3; }
ListElement { role_display: "One"; role_value: 4; }
ListElement { role_details: "Two"; role_value: 5; }
ListElement { role_details: "Three"; role_value: 6; }
ListElement { role_details: "Four"; role_value: 7; }
ListElement { role_details: "Five"; role_value: 8; }
ListElement { role_details: "Six"; role_value: 9; }
ListElement { role_keyID: "Seven"; role_value: 10; }
ListElement { role_keyID: "Eight"; role_value: 11; }
ListElement { role_keyID: "hello"; role_value: 12; }
}
DelegateModel {
id: displayDelegateModel
delegate: displayDelegate
model: myModel
groups: [
DelegateModelGroup {
includeByDefault: false
name: "displayField"
}
]
filterOnGroup: "displayField"
Component.onCompleted: {
var rowCount = myModel.count;
items.remove(0,rowCount);
for( var i = 0;i < rowCount;i++ ) {
var entry = myModel.get(i);
if(entry.role_display !== undefined) {
items.insert(entry, "displayField");
}
}
}
}
Component{
id: displayDelegate
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: root.rowHeight
color: "green"
border.color: "black"
Text {
text: role_display
anchors.centerIn: parent
}
}
}
ListView {
id: displayListView
anchors.left: parent.left
anchors.right: parent.right
height: root.rowHeight * 4
model: displayDelegateModel
}
}
}
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("DelegateModel Sample 1")
width: 640
height: 600
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
Item {
id: root
anchors.fill: parent
readonly property int rowHeight: 25
ListModel {
id: myModel
ListElement { role_display: "One"; role_value: 0; }
ListElement { role_display: "One"; role_value: 2; }
ListElement { role_display: "One"; role_value: 3; }
ListElement { role_display: "One"; role_value: 4; }
ListElement { role_details: "Two"; role_value: 5; }
ListElement { role_details: "Three"; role_value: 6; }
ListElement { role_details: "Four"; role_value: 7; }
ListElement { role_details: "Five"; role_value: 8; }
ListElement { role_details: "Six"; role_value: 9; }
ListElement { role_keyID: "Seven"; role_value: 10; }
ListElement { role_keyID: "Eight"; role_value: 11; }
ListElement { role_keyID: "hello"; role_value: 12; }
}
DelegateModel {
id: displayDelegateModel
delegate: displayDelegate
model: myModel
groups: [
DelegateModelGroup {
includeByDefault: false
name: "displayField"
}
]
filterOnGroup: "displayField"
Component.onCompleted: {
var rowCount = myModel.count;
items.remove(0,rowCount);
for( var i = 0;i < rowCount;i++ ) {
var entry = myModel.get(i);
if(entry.role_display !== undefined) {
items.insert(entry, "displayField");
}
}
}
}
Component{
id: displayDelegate
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: root.rowHeight
color: "green"
border.color: "black"
Text {
text: role_display
anchors.centerIn: parent
}
}
}
ListView {
id: displayListView
anchors.left: parent.left
anchors.right: parent.right
height: root.rowHeight * 4
model: displayDelegateModel
}
}
}
What about C++?
Well this is all fine and dandy however what if I'm using a C++ model instead of the ListModel QML component?
That is a good question; The issue I ran into was that the ListModel and QAbstractItemModel interfaces are quite different, the QAbstractItemModel does not have a get() method like the ListModel does and we're using that in our Component.OnComplete() handler. It took a while to figure out what the ListModel::get() method was returning but with the help of some colleges we found out that its returning a QVariantMap where the key is the QByteArray value which is defined in the QAbstractItemModel::roleNames() method.
So lets add a C++ view model to our sample project and use it instead of the ListModel we've been using to date. Since the above is a Qt Quick UI project we'll need to create a new Qt Quick Application (*.pro) project, copy the QML script DelegateModel_Smaple1.qml over to it and add a new C++ class called MyViewModel. Have the MyViewModel class extend the QStandardItemModel which is a template Qt Model from Qt's Model View Architecture.
This class will contain our data and we can assign different attributes to the data in the collection via Roles ( think of roles as attributes ). This is the same as we did in the ListModel except in C++ we define them as an enum and use them in the setData() and data() methods when entering and retrieving data into/from the collection. In order for the QML scripts to be able to reference these attributes without having to explicitly invoke a method call we create a QHash that maps enum values to strings where the C++ uses the enum values and the QML scripts use the string values; this is done within the virtual method QAbstractItemModel::roleNames.
I'll go over all this in more detail in a future post dedicated to creating C++ models for Qt's Model View Architecture but for now lets gloss over it and get back to this tutorial on the DelegateModelGroup.
So that we don't have to change our QML script to much we need to make the interface of our MyViewModel (which extends QStandardItemModel which is a concrete version of the QAbstractItemModel) match that of the ListModel that we were using to date. To do this we need to add a count property (via the Q_PROPERTY macro) and a get() method (via the Q_INVOKABLE macro).
The count property is pretty straight forward if you've read my previous tutorial Qt Quick 102: Introducing C++ to QML, we simply declare the property via the Q_PROPERTY macro so the QML scripts know about it and the slot that gets invoked when the QML scripts access the property simply ask the model how many items are in its collection.
In the MyViewModel header we declare the count property like so:
view plaincopy to clipboardprint?
Q_PROPERTY( int count READ getRowCount() NOTIFY rowCountChanged() )
Q_PROPERTY( int count READ getRowCount() NOTIFY rowCountChanged() )
count: is the name of the property which matches the property of the ListModel class so that the QML does not have to change.
getRowCount(): is a slot which asks the model its row count ( this->rowCount(); ) which is just the base implementation of QStandardItemModel.
rowCountChanged(): is a signal that MyViewModel emits whenever it adds or removes data form its collection ( this is a signal we have to emit manually the QStandardItemModel doesn't know about it; see the MyViewModel::initialize() method)
The real trick for me was the get() method; not that its a hard method to implement, far from it, but that it was hard to figure out what the architecture was looking for.
As part of the QStandardItemModel (or rather the QAbstractItemModel) there is a virtual method called roleNames() which the glue between the View and the ViewModel use to map the references used by the view to values the C++ model can use (string to enum). If we define custom attributes (Roles) that we want to use in our business logic and in the view we need to re-implement this method to include them so that the glue between the View and the ViewModel knows how to map the references.
view plaincopy to clipboardprint?
QHash<int,QByteArray> MyViewModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[MyViewModel_Roles::MyViewModel_Roles_Display] = "role_display";
roles[MyViewModel_Roles::MyViewModel_Roles_Details] = "role_details";
roles[MyViewModel_Roles::MyViewModel_Roles_KeyId] = "role_keyid";
roles[MyViewModel_Roles::MyViewModel_Roles_Value] = "role_value";
return roles;
}
QHash<int,QByteArray> MyViewModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[MyViewModel_Roles::MyViewModel_Roles_Display] = "role_display";
roles[MyViewModel_Roles::MyViewModel_Roles_Details] = "role_details";
roles[MyViewModel_Roles::MyViewModel_Roles_KeyId] = "role_keyid";
roles[MyViewModel_Roles::MyViewModel_Roles_Value] = "role_value";
return roles;
}
The MyViewModel::get() method uses these so that it can return an object to the View that uses the same mapping. Again this it to match what the ListModel was doing so that we don't have to change our QML scripts very much.
view plaincopy to clipboardprint?
QVariantMap MyViewModel::get( int rowNumber ) const
{
QVariantMap map;
QHash<:int,QByteArray> roleName = roleNames();
foreach (int i, roleName.keys())
{
// For each attribute (role) get its value and insert it into the map
// where the map's key is the attributes string reference
// The data() method returns the value for the requested attribute
// where i is the attributes enum value.
// The index() method returns a QModelIndex which is a further
// abstraction layer (will talk about that in a later post)
map[roleName.value(i)] = data( index( rowNumber,0 ), i );
}
return map;
}
QVariantMap MyViewModel::get( int rowNumber ) const
{
QVariantMap map;
QHash<:int,QByteArray> roleName = roleNames();
foreach (int i, roleName.keys())
{
// For each attribute (role) get its value and insert it into the map
// where the map's key is the attributes string reference
// The data() method returns the value for the requested attribute
// where i is the attributes enum value.
// The index() method returns a QModelIndex which is a further
// abstraction layer (will talk about that in a later post)
map[roleName.value(i)] = data( index( rowNumber,0 ), i );
}
return map;
}
Here we are returning a QVariantMap object back to the view; the keys in this map match the keys the QML script uses when displaying the collection of data from the view model when put inside a view such as a ListView. This way the return from this method can be used like so returnObject.role_display.
On the QML side of things we only have to change the Component.onCompleted() handlers a little bit in order to point it at the MyViewModel (C++) vs. the ListModel (QML).
view plaincopy to clipboardprint?
Component.onCompleted: {
var rowCount = MyModel.count;
items.remove(0,rowCount);
for( var i = 0;i < rowCount;i++ )
{
var entry = MyModel.get(i);
if(entry.role_display !== undefined){
items.insert(entry, "displayField");
}
}
}
Component.onCompleted: {
var rowCount = MyModel.count;
items.remove(0,rowCount);
for( var i = 0;i < rowCount;i++ )
{
var entry = MyModel.get(i);
if(entry.role_display !== undefined){
items.insert(entry, "displayField");
}
}
}
Notice that all we needed to do here is change the keyword myModel to MyModel where myModel was the id of the ListModel and MyModel is a reference to a root context which we set in the main.cpp file.
view plaincopy to clipboardprint?
MyViewModel* viewModel = new MyViewModel();
viewModel->initialize();
QQmlApplicationEngine engine;
//"MyModel" is a string reference that can be used in QML to look up this object.
engine.rootContext()->setContextProperty("MyModel", viewModel );
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
MyViewModel* viewModel = new MyViewModel();
viewModel->initialize();
QQmlApplicationEngine engine;
//"MyModel" is a string reference that can be used in QML to look up this object.
engine.rootContext()->setContextProperty("MyModel", viewModel );
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
When this version of the sample app is executed you'll get the exact same behaviour; Sample 1 and Sample 2 look and behave exactly the same the only difference is that one is using a ListModel (QML) and another is using MyViewModel (C++).
Ok so that is how you use the DelegateModelGroup in order to allow a View to only show a sub set of the data contained within the root Data Model.
You can download both samples which illustrates the use of a DelegateModelGroup here:
Sample 1: All QML
Sample 2: C++ ViewModel
Sample 3: All QML, One ListView, One DelegateModel, Multiple DelegateModelGroups
Thank you I hope you have enjoyed and found this tutorial helpful. Feel free to leave any comments or questions you might have below and I’ll try to answer them as time permits.
Until next time think imaginatively and design
一个QMLListView的例子--的更多相关文章
- 用一个简单的例子来理解python高阶函数
============================ 用一个简单的例子来理解python高阶函数 ============================ 最近在用mailx发送邮件, 写法大致如 ...
- Spring-Context之一:一个简单的例子
很久之前就想系统的学习和掌握Spring框架,但是拖了很久都没有行动.现在趁着在外出差杂事不多,就花时间来由浅入深的研究下Spring框架.Spring框架这几年来已经发展成为一个巨无霸产品.从最初的 ...
- 高仿“点触验证码”做的一个静态Html例子
先上源码: <html> <head> <title>TouClick - Designed By MrChu</title> <meta htt ...
- 关于apriori算法的一个简单的例子
apriori算法是关联规则挖掘中很基础也很经典的一个算法,我认为很多教程出现大堆的公式不是很适合一个初学者理解.因此,本文列举一个简单的例子来演示下apriori算法的整个步骤. 下面这个表格是代表 ...
- 一个UWSGI的例子
摘要:uwsgi执行顺序:启动master进程,执行python脚本的公共代码(import同一层).然后生成worker进程,uwsgi.post_fork_hook=init_functions, ...
- 扩展Python模块系列(二)----一个简单的例子
本节使用一个简单的例子引出Python C/C++ API的详细使用方法.针对的是CPython的解释器. 目标:创建一个Python内建模块test,提供一个功能函数distance, 计算空间中两 ...
- fitnesse - 一个简单的例子(slim)
fitnesse - 一个简单的例子(slim) 2017-09-30 目录1 编写测试代码(Fixture code)2 编写wiki page并运行 2.1 新建wikiPage 2.2 运行 ...
- Struts2的配置和一个简单的例子
Struts2的配置和一个简单的例子 笔记仓库:https://github.com/nnngu/LearningNotes 简介 这篇文章主要讲如何在 IntelliJ IDEA 中使用 Strut ...
- 一个简单的例子搞懂ES6之Promise
ES5中实现异步的常见方式不外乎以下几种: 1. 回调函数 2. 事件驱动 2. 自定义事件(根本上原理同事件驱动相同) 而ES6中的Promise的出现就使得异步变得非常简单.promise中的异步 ...
随机推荐
- poj1971Parallelogram Counting
链接 越来越感觉到了数学的重要性!.. 这题本来用以斜率和长度为key值进行hash不过感觉很麻烦还TLE了.. 最后知道中点一样的话就可以组成平行四边形,初中数学就可以了.. #include &l ...
- Python类、模块、包的区别
类 类的概念在许多语言中出现,很容易理解.它将数据和操作进行封装,以便将来的复用. 模块 模块,在Python可理解为对应于一个文件.在创建了一个脚本文件后,定义了某些函数和变量.你在其他需要这些功能 ...
- PPPOE协议
PPPOE协议 PPPOE的全称为PPP Over Ethernet,用于实现PPP在以太网上的传输.它要求通信双方是点到点的关系,不适于广播型的以太网和一些多点访问型网络. 一.PPPOE的作用 将 ...
- JavaWeb 5 Tomcat
5 Tomcat 1 Web开发入门 1.1 引入 之前的程序: java桌面程序,控制台控制,socket gui界面.javase规范 ...
- Android 热修复Nuwa的原理及Gradle插件源码解析
现在,热修复的具体实现方案开源的也有很多,原理也大同小异,本篇文章以Nuwa为例,深入剖析. Nuwa的github地址 https://github.com/jasonross/Nuwa 以及用于 ...
- SDL1.2到2.0的迁移指南(转)
里面有些单词不好翻译所以放在开头,以备查验. BLock Image Transfer, a computer graphics operation in which two bitmap patte ...
- OpenGL的glTranslatef平移变换函数详解
OpenGL的glTranslatef平移变换函数详解 glTranslated()和glTranslatef()这两个函数是定义一个平移矩阵,该矩阵与当前矩阵相乘,使后续的图形进行平移变换. 我们先 ...
- OpenGL的gluLookAt观察变换函数详解
void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble center ...
- C#_接口
.Net提供了接口,这个不同于Class或者Struct的类型定义.接口有些情况,看似和抽象类一样,因此有些人认为在.Net可以完全用接口来替换抽象类.其实不然,接口和抽象类各有长处和缺陷,因此往往在 ...
- java 多线程4(死锁)
死锁现象: 死锁原因: 1.存在两个或两个以上的线程. 2.存在两个或两个或两个以上的共享资源. 死锁现象解决的方案: 没有方案只能尽量避免.