How to use Qt’s QSortFilterProxyModel

来源:互联网 发布:淘宝卖虚拟商品 编辑:程序博客网 时间:2024/06/02 13:12

In this tutorial I’m going to show you how to use Qt’s QSortFilterProxyModel to only present a subset of data to the presentation layer.

In Qt Quick 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 subset of that data; In order to achieve this we need to present each View with a subset of the contents of the Data Model. We can do this via a Proxy Model such as the QSortFilterProxyModel.

In Qt there are classes called models which implement interfaces that allows them to satisfy all the hooks required by the framework so that views can query them for data and so they can be notified when that data changes. The base class for all models that exist in Qt is called the QAbstractItemModel, this model class has all the required methods and their base implementation satisfies the minimum required to allow views to hook up to them. Where the QAbstractItemModel is used as a base class for the sole purpose of presenting a collection of data to the presentation layer it is also inherited by a specialized class called the QAbstractProxyModel whose sole role is to allow the data to be manipulated without affecting the source data model; this way the source data model can be used by any number of different proxy modes and each proxy model can manipulate the data in different ways without effecting each other. The QAbstractProxyModel as the name implies is a general base class with just enough implementation to satisfy the requirements of allowing the data to be manipulated without effecting the source. Qt has a specialized class which inherits from the QAbstractProxyModel class called theQSortFilterProxyModel which can be used to sort and/or filter the data in the source model without effecting the source model it self. By having your view access a QSortFilterProxyModel instead of the QAbstractItemModel directly you can present your view with a subset of the data actually in the source data model.

Note the base implementation set in the QAbstractProxyModel also listens to all the data changed signals that the source model can emit so that if new entries are added to the source model, or if existing entries are removed or modified the proxy model will relay those signals to the view so the view knows to update. You don’t have to manually connect these signals it is done for you out of the box.QSortFilterProxyModel_Sample1 illistrates this by having a button which will tell the source model to modify some entries (some even make the entry no longer pass the filter logic and therefore get removed from the proxy model) and show that the view updates to reflect the changes without any manual signal connection required.

Lets get started with some code shall we.

First create a new Qt Quick Application (*.pro) project, remember this is a type of project that can have both C++ and QML. Call your new project QSortFilterProxyModel_Sample1.

First lets add a new C++ class called MyViewModel which we’ll use as our source data model. Have it extend the QStandardItemModel (which is a specialized version of the QAbstractItemModel) and define an enum that will speicify some custom user roles. We’ll also have to override the virtual methodroleNames so that we can define the reference keywords that the View can use to access each one of these custom roles.

view plaincopy to clipboardprint?
  1. #include <QStandardItemModel>  
  2. #include <QObject>  
  3.   
  4. class MyViewModel : public QStandardItemModel {  
  5.     Q_OBJECT  
  6. public:  
  7.     enum MyViewModel_Roles  
  8.     {  
  9.         MyViewModel_Roles_Display = Qt::UserRole + 1,   /**< This role holds the display string for an entry in the model. */  
  10.         MyViewModel_Roles_Details,                      /**< This role holds the details string for an entry in the model. **/  
  11.         MyViewModel_Roles_KeyId,                        /**< This role holds the key id for an entry in the model. **/  
  12.         MyViewModel_Roles_Value,                        /**< This role holds the value for an entry in the model. **/  
  13.     };  
  14.   
  15.     virtual QHash<int,QByteArray> roleNames() const {  
  16.         QHash<int, QByteArray> roles;  
  17.         roles[MyViewModel_Roles_Display] = "role_display";  
  18.         roles[MyViewModel_Roles_Details] = "role_details";  
  19.         roles[MyViewModel_Roles_KeyId] = "role_keyid";  
  20.         roles[MyViewModel_Roles_Value] = "role_value";  
  21.         return roles;  
  22.     }  
  23. }  

In the constructor lets populate this model with some dummy data for use in this tutorial. Lets define 12 entries 4 will have a value assigned to the MyViewModel_Roles_Display role, 5 will have a value assigned to the MyViewModel_Roles_Details role, 3 will have a value assigned to the MyViewModel_Roles_KeyId role, and all will have a value assigned to the MyViewModel_Roles_Value role where half will have an even value and half will have an odd value.

view plaincopy to clipboardprint?
  1. // invisibleRootItem is part of the QStandardItemModel and is a simply way of getting the root of the model.  
  2. auto root = this->invisibleRootItem();       
  3.   
  4. // Create a new entry for the model  
  5. QStandardItem* entry = new QStandardItem();            
  6. entry->setData("One", MyViewModel_Roles_Display );  
  7. entry->setData(0, MyViewModel_Roles_Value );  
  8. root->appendRow(entry);    // Append it to the root (this will be entry #1 at index/row zero (0) )  
  9.   
  10. entry = new QStandardItem();  
  11. entry->setData("One", MyViewModel_Roles_Display );  
  12. entry->setData(2, MyViewModel_Roles_Value );  
  13. root->appendRow(entry); // Index/Row 1  
  14.   
  15. entry = new QStandardItem();  
  16. entry->setData("One", MyViewModel_Roles_Display );  
  17. entry->setData(3, MyViewModel_Roles_Value );  
  18. root->appendRow(entry); // Index/Row 2  
  19.   
  20. entry = new QStandardItem();  
  21. entry->setData("One", MyViewModel_Roles_Display );  
  22. entry->setData(4, MyViewModel_Roles_Value );  
  23. root->appendRow(entry); // Index/Row 3  
  24.   
  25. entry = new QStandardItem();  
  26. entry->setData("Two", MyViewModel_Roles_Details );  
  27. entry->setData(5, MyViewModel_Roles_Value );  
  28. root->appendRow(entry); // Index/Row 4  
  29.   
  30. entry = new QStandardItem();  
  31. entry->setData("Three", MyViewModel_Roles_Details );  
  32. entry->setData(6, MyViewModel_Roles_Value );  
  33. root->appendRow(entry); // Index/Row 5  
  34.   
  35. entry = new QStandardItem();  
  36. entry->setData("Four", MyViewModel_Roles_Details );  
  37. entry->setData(7, MyViewModel_Roles_Value );  
  38. root->appendRow(entry); // Index/Row 6  
  39.   
  40. entry = new QStandardItem();  
  41. entry->setData("Five", MyViewModel_Roles_Details );  
  42. entry->setData(8, MyViewModel_Roles_Value );  
  43. root->appendRow(entry); // Index/Row 7  
  44.   
  45. entry = new QStandardItem();  
  46. entry->setData("Six", MyViewModel_Roles_Details );  
  47. entry->setData(9, MyViewModel_Roles_Value );  
  48. root->appendRow(entry); // Index/Row 8  
  49.   
  50. entry = new QStandardItem();  
  51. entry->setData("Seven", MyViewModel_Roles_KeyId );  
  52. entry->setData(10, MyViewModel_Roles_Value );  
  53. root->appendRow(entry); // Index/Row 9  
  54.   
  55. entry = new QStandardItem();  
  56. entry->setData("Eight", MyViewModel_Roles_KeyId );  
  57. entry->setData(11, MyViewModel_Roles_Value );  
  58. root->appendRow(entry); // Index/Row 10  
  59.   
  60. entry = new QStandardItem();  
  61. entry->setData("hello", MyViewModel_Roles_KeyId );  
  62. entry->setData(12, MyViewModel_Roles_Value );  
  63. root->appendRow(entry); // Index/Row 11  

In the main.cpp file we need to instantiate an instance of this class and assign it to the root context so that the view can access it. Note to add a root context you need to include the QQmlContext header.

view plaincopy to clipboardprint?
  1. #include <QApplication>  
  2. #include <QQmlContext>  
  3. #include <QQmlApplicationEngine>  
  4. #include "MyViewModel.h"  
  5.   
  6. int main(int argc, char *argv[])  
  7. {  
  8.     QApplication app(argc, argv);  
  9.   
  10.     // Create an instance of our ViewModel; this holds the total list of data  
  11.     auto vm = new MyViewModel();  
  12.   
  13.     QQmlApplicationEngine engine;  
  14.     engine.rootContext()->setContextProperty("MyModel", vm );  
  15.     engine.load(QUrl(QStringLiteral("qrc:///main.qml")));  
  16.   
  17.     return app.exec();  
  18. }  

The setContextProperty() method is a way of setting a global reference that can be used by views to access instances of objects created in the C++ side. The setContextProperty() method takes two arguments; the first is a string which will be the reference keyword used in the QML script to refer to this object and the second is the reference (pointer) to the object it self. In the above whenever the QML Engine reads the keyword MyModel in the QML script it will refer to this instance of the MyViewModel class. This is just one of the many ways to allow QML scripts to refer to C++ objects.

Now that we have a model lets create a view to show it in. In the main.qml file that gets auto generated per the Qt Quick Application project template add the following ListView component to show the data stored in the model.

view plaincopy to clipboardprint?
  1. Item {  
  2.     id: root  
  3.     anchors.fill: parent  
  4.   
  5.     ListView {  
  6.         anchors.fill: parent  
  7.         model: MyModel  
  8.         delegate: myDelegate  
  9.     }  
  10.     Component {  
  11.         id: myDelegate  
  12.         Rectangle {  
  13.             anchors.left: parent.left  
  14.             anchors.right: parent.right  
  15.             height: 25  
  16.             color: "green"  
  17.             border.color: "black"  
  18.   
  19.             Text {  
  20.                 text: role_value  
  21.                 anchors.centerIn: parent  
  22.             }  
  23.         }  
  24.     }  
  25. }  

If you run this you will get the following list of all 12 entries showing the data stored in each entries MyViewModel_Roles_Value role (or attribute if you prefer to think of it that way).

QSortFilterProxyModel_Sample1

Now lets say we only want to show the entries who have the Details role value set, how would we do that?

Enter the QSortFilterProxyModel

In the main.cpp file lets instantiate a new instance of the QSortFilterProxyModel and set it to filter out items that don't have a MyViewModel_Roles_Details attribute with some value (if the entry does not have that attribute its value will be blank).

view plaincopy to clipboardprint?
  1. #include <QApplication>  
  2. #include <QQmlContext>  
  3. #include <QQmlApplicationEngine>  
  4. #include "MyViewModel.h"  
  5.   
  6. int main(int argc, char *argv[])  
  7. {  
  8.     QApplication app(argc, argv);  
  9.   
  10.     // Create an instance of our ViewModel; this holds the total list of data  
  11.     auto vm = new MyViewModel();  
  12.   
  13.     auto detailsProxyModel = new QSortFilterProxyModel();  
  14.     detailsProxyModel->setFilterRole( MyViewModel::MyViewModel_Roles::MyViewModel_Roles_Details);  
  15.     detailsProxyModel->setFilterRegExp( "^\\S+$" );  
  16.     detailsProxyModel->setSourceModel( vm );  
  17.   
  18.     QQmlApplicationEngine engine;  
  19.     engine.rootContext()->setContextProperty("MyModel", vm );  
  20.     engine.rootContext()->setContextProperty("DetailsRoleModel", detailsProxyModel );  
  21.     engine.load(QUrl(QStringLiteral("qrc:///main.qml")));  
  22.   
  23.     return app.exec();  
  24. }  

Notice here that we use the QSortFilterPorxyModel method setFilterRole() to tell the proxy what value to use when check if the entry should be included in the proxy and we use the setFilterRegExp() method to tell the proxy how to evaluate the value. So above we are saying that for every entry found in the source model look at the value it has stored in the MyViewModel_Roles_Details role and check if it passes the given regular expression; if so then include it in the proxy model, if not then ignore it (like it doesn't exist).

To visualize it below is a diagram of what our MyViewModel class looks like when fully populated:
QSortFilterProxyModel_12EntryModelDiagram

And here is what the detailsProxyModel instance looks like after we set its source model:
QSortFilterProxyModel_DetailsProxyModelDiagram

You can see here that Entries 1-4 and Entries 10-12 when the proxy model asks them for the value they have in the MyViewModel_Roles_Details role they will return a blank string (because they don't have that role defined) which does not satisfy the regular expression "^\\S+$" that we told the proxy model to use when evaluating entries. As a result the proxy model will skip them. If I were to ask the MyViewModel object for its row count it will return 12 however the detailsProxyModel will return only 5 because it is ignoring all entries which don't satisfy the set regular expression. The base implementations in the QSortFilterProxyModel for the parent(), index(), rowCount(), columnCount(), mapToSource(), mapFromSource(), data(), setData(), etc. will all ignore any entries that don't satisfy the filter. So if you ask the MyViewModel object to get you the entry at row 0 it will return Entry 1 however if you ask the detailsProxyModel for the entry at row 0 it will return you Entry 5.

Now that we have a proxy model which only presents the view with the entries who have a value in the MyViewModel_Roles_Details role we can update our view to display that value (we will no longer get blank boxes because all entries in the proxy model will have a value).

view plaincopy to clipboardprint?
  1. Item {  
  2.     id: root  
  3.     anchors.fill: parent  
  4.   
  5.     ListView {  
  6.         id: detailsListView  
  7.         anchors.fill: parent  
  8.         model: DetailsRoleModel  
  9.         delegate: detailsDelegate  
  10.     }  
  11.     Component {  
  12.         id: detailsDelegate  
  13.         Rectangle {  
  14.             anchors.left: parent.left  
  15.             anchors.right: parent.right  
  16.             height: 25  
  17.             color: "yellow"  
  18.             border.color: "black"  
  19.   
  20.             Text {  
  21.                 text: role_details  
  22.                 anchors.centerIn: parent  
  23.             }  
  24.         }  
  25.     }  
  26. }  

QSortFilterProxyModel_Sample1_Details

That's great and all but what if my filter if more elaborate then a regular expression?

Out of the box the QSortFilterProxyModel can filter roles or columns and can evaluate the data based on a string pattern, regular expressions, or wildcard characters. If however your filter is more elaborate, perhaps you need to check two values (i.e. two different roles like show me entries who have the MyViewModel_Roles_Details role and have an even MyViewModel_Roles_Value role) your going to have to start overriding the base implementation.

Enter the filterAcceptsRow() Virtual Method.

The QSortFilterProxyModel exposes a virtual method called filterAcceptsRow() whose base implementation checks the set column or role against the given filter (string, wildcard, regular expression). This method is a predicate which simply asks for a yes or no response of should I include this row in my proxy model; If the method returns false the proxy model will ignore the entry. You can override this method and replace it with your own implementation as long as your implementation returns a simply yes or no (include or not to include).


转载:http://imaginativethinking.ca/use-qt-qsortfilterproxymodel/

0 0
原创粉丝点击