Files:
The Vegetables DataModel example demonstrates how to implement a custom DataModel.
In this example we'll learn how to implement a custom, hierarchical DataModel that shows a list of vegetables grouped by their color.
The UI of this sample application consists of a ListView that simply shows the content of our custom model.
We create the model in C++ and export it to QML, under the name '_model', as context property inside the main.cpp function.
QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(&app); qml->setContextProperty("_model", new VegetablesDataModel(&app));
Inside the main QML document, we simply bind this exported property against the 'dataModel' property of the ListView.
ListView { horizontalAlignment: HorizontalAlignment.Center dataModel: _model }
The data we want to represent with our model look like the following
- Green - Cucumber - Peas - Salad - Red - Tomato - Red Radish - Carrot - Yellow - Corn - Paprika
So we have three top-level entries (the colors) and 3, 3 and 2 second-level entries (the vegetables). By default the ListView interprets the top-level entries of a model as headers and the second-level entries as normal items. This behavior can be modified by defining an item type though.
Like any data model in Cascades, our model must inherit from the abstract class bb::cascades::DataModel and reimplement the abstract methods.
class VegetablesDataModel : public bb::cascades::DataModel { Q_OBJECT public: VegetablesDataModel(QObject *parent = 0); // Required interface implementation virtual int childCount(const QVariantList& indexPath); virtual bool hasChildren(const QVariantList& indexPath); virtual QVariant data(const QVariantList& indexPath); virtual QString itemType(const QVariantList& indexPath); };
Inside the constructor we just pass the parent parameter to the constructor of our base class, so that the model can be part of an QObject tree.
VegetablesDataModel::VegetablesDataModel(QObject *parent) : bb::cascades::DataModel(parent) { }
The first abstract method we have to reimplement is childCount(), which is called by the ListView to find out how many child items a certain parent item in the model has. The passed 'indexPath' parameter specifies the parent item.
If the length of the 'indexPath' is 0 (that means an empty indexPath has been passed), we return the number of top-level items in our model (which is '3' for the three colors). Otherwise if the length of the 'indexPath' is 1 (that means the ListView asks for the number of child items for a top-level item), we return the number of second-level items, depending on the parent item.
int VegetablesDataModel::childCount(const QVariantList& indexPath) { /** * In this very simple data model, the first two headers has 3 children * and the third one has 2. */ const int level = indexPath.size(); if (level == 0) { // The number of top-level items is requested return 3; // headers "Green", "Red" and "Yellow" } if (level == 1) { // The number of child items for a header is requested const int header = indexPath[0].toInt(); if (header == 0 || header == 1) // "Green" or "Red" return 3; else // "Yellow" return 2; } // The number of child items for 2nd level items is requested -> always 0 return 0; }
Sometimes the ListView only wants to know whether an item has child items or not and is not interested in the actual number of child items. In this case it calls the hasChildren() method on the model.
The implementation is straight forward in our example, since all top-level items have children. So we return 'true' if hasChildren() is called on a top-level item and 'false' in all other cases.
bool VegetablesDataModel::hasChildren(const QVariantList& indexPath) { // Performance is not an issue with this data model. // So just let childCount tell us if we have children. return childCount(indexPath) > 0; }
Since the ListView can use different visual representations for the different types of items (e.g. headers and normal items), our model has to tell the ListView which item is of which type. This is done by reimplementing the itemType() method. Depending on the length of the passed 'indexPath' we simply return the strings "header" for top-level items and "item" for second-level items.
QString VegetablesDataModel::itemType(const QVariantList& indexPath) { switch (indexPath.size()) { case 0: return QString(); break; case 1: return QLatin1String("header"); break; default: return QLatin1String("item"); break; } }
While the previous methods are called by the ListView to determine the structure of the model, the data() method is called to retrieve the actual data. An 'indexPath' parameter is passed again to specify the actual item the data shall be retrieved for. Normally you would use this index path to look up the data in some internal objects where you keep your data. In this example however the code is quite verbose to show in detail how to return the right data depending on the passed 'indexPath'.
So if the size of the 'indexPath' is 1, the data of a top-level item are requested. So depending on the actual value of the first 'indexPath' component, we return the color as string. If the size of the 'indexPath' is 2, the data of a second-level item are requested. Now we use a nested switch statement to cover all possible 'indexPath' values.
QVariant VegetablesDataModel::data(const QVariantList& indexPath) { QString value; if (indexPath.size() == 1) { // Header requested switch (indexPath[0].toInt()) { case 0: value = tr("Green"); break; case 1: value = tr("Red"); break; case 2: value = tr("Yellow"); break; } } if (indexPath.size() == 2) { // 2nd-level item requested const int header = indexPath[0].toInt(); const int childItem = indexPath[1].toInt(); switch (header) { case 0: // "Green" switch (childItem) { case 0: value = tr("Cucumber"); break; case 1: value = tr("Peas"); break; case 2: value = tr("Salad"); break; } break; case 1: // "Red" switch (childItem) { case 0: value = tr("Tomato"); break; case 1: value = tr("Red Radish"); break; case 2: value = tr("Carrot"); break; } break; case 2: // "Yellow" switch (childItem) { case 0: value = tr("Corn"); break; case 1: value = tr("Paprika"); break; } break; } } qDebug() << "Data for " << indexPath << " is " << value; return QVariant(value); }