一个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 MyViewModel::roleNames() const
{
QHash 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 MyViewModel::roleNames() const
{
QHash 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&lt: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&lt: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

你可能感兴趣的:(ListView)