Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Native Development Knowledge Base

Using your own DataModel

by Retired on ‎06-20-2012 05:42 PM - edited on ‎09-04-2012 02:22 PM by Retired (8,457 Views)

Introduction

ListView gets its data from a data model.  Cascades™ provides GroupDataModel and QListDataModel, and these are sufficient for many needs. But if you have data that doesn't quite fit either of these, it's easy to make your own.

 

In this article we populate a list with some trivial "hello world" data using our own subclass of bb::cascades::DataModel.

 

HelloWorldDataModel App Step By Step

The app will be just a single list with two headers: "hello" and "world".   Header "hello" will have 2 children: "hello_0" and "hello_1".   Header "world" will have 3 children: "world_0", "world_1", "world_2".

 

To create the app as we go, start by creating a new Blackberry® Cascades C++ project as a Standard empty project. We assume you know how to create a project and add a new class, and that you understand the basics of Momentics and C++.

 

If you prefer to use .h to .hpp,  rename app.hpp and change both affected #includes.

 

HelloWorldDataModel declaration

A quick look at bb/cascades/DataModel shows we need to override three methods: childCount(), hasChildren(), and data().  Create a new class, subclass from bb/cascades/DataModel, and add the three required overloads.  Your data model class declaration should look something like:

 

/*
 * HelloWorldDataModel.h
 */

#ifndef HELLOWORLDDATAMODEL_H_
#define HELLOWORLDDATAMODEL_H_

#include <bb/cascades/DataModel>

class HelloWorldDataModel : public bb::cascades::DataModel
{
public:
    HelloWorldDataModel();
    virtual ~HelloWorldDataModel();

    // Required interface implementation
    virtual int childCount(const QVariantList& indexPath);
    virtual bool hasChildren(const QVariantList& indexPath);
    virtual QVariant data(const QVariantList& indexPath);
};

#endif /* HELLOWORLDDATAMODEL_H_ */

Note that it’s impolite to use “using namespace” inside a .h file, as this forces the using on every cpp file that includes the include file, whether the cpp file does so explicitly or through cascaded includes.  So we explicitly scope the superclass instead.

 

HelloWorldDataModel implementation

Our HelloWorldDataModel will have two headers: "hello" and "world".   Header "hello" will have 2 children: "hello_0" and "hello_1".   Header "world" will have 3 children: "world_0", "world_1", "world_2"

 

In HelloWorldDataModel.cpp, implement childCount() to return 2 children for the header, 2 children of the first header, 3 children for the second header.  There are no grandchildren of headers. A header has indexPath.size() == 1.

 

The overload hasChildren() is logically equivalent to childCount(indexPath) > 0. The reason for this overload is for performance: we can often do a quick check to see if an index path has any children, and this may be significantly faster than counting how many children we actually have.  Our case is sufficiently simple that the obvious initial implementation is sufficient.

 

Finally, set up data() to return headers “hello” or “world” when the indexPath has only one entry, and items “hello_0”, “hello_1” etc. or “world_0”, “world_1” etc. when the indexPath has more than one entry. For this data model, if we ask for data we’ll return something even if it is out of range; ListView will honour the result of hasChildren and childCount.  A productized version would likely add defensive code here, but for this article we'll rely on childCount.

 

Your HelloWorldDataModel.cpp should look something like:

/*
 * HelloWorldDataModel.cpp
 */

#include "HelloWorldDataModel.h"

HelloWorldDataModel::HelloWorldDataModel()
{
}

HelloWorldDataModel::~HelloWorldDataModel()
{
}

int HelloWorldDataModel::childCount(const QVariantList& indexPath)
{
    // In this very simple data model,
    // the first header has 2 children and the second has 3.
    int level = indexPath.size();
    if (level == 0)
    {
        return 2; // headers "hello" and "world"
    }

    if (level == 1)
    {
        return 2 + indexPath[0].toInt();
    }
    return 0;
}

bool HelloWorldDataModel::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;
}

/*
 * Return data as a string QVariant for any requested indexPath.
 * We could add defensive code to ensure that the data is not
 * out of range, but ListView honours the results of hasChildren
 * and childCount.
 */
QVariant HelloWorldDataModel::data(const QVariantList& indexPath)
{
    // Data is hard-coded
    //  "hello"
    //     "hello_0"
    //     "hello_1"
    //  "world"
    //     "world_0"
    //     "world_1"
    //     "world_2"
    QString value;
    if (indexPath.size() > 0)
    {
        switch (indexPath[0].toInt())
        {
            case 0:  {value = "hello"; break;}
            case 1:  {value = "world"; break;}
            default: {value = "unexpected"; break;}
        }
        if (indexPath.size() > 1)
        {
            value += QString("_%1").arg(indexPath[1].toInt());
        }
    }
    qDebug() << "Data for " << indexPath << " is " << value;
    return QVariant(value);
}

 

Display the data model

The data model is now ready!

 

To use this in QML, we need to register the type.  We will add this to custom.lib version 1.0.  In app.cpp, include the data model .h and the registration line:

#include "HelloWorldDataModel.h"

App::App()
{
    qmlRegisterType<HelloWorldDataModel>("custom.lib", 1, 0, "HelloWorldDataModel");

    QmlDocument *qml = QmlDocument::create("main.qml");
    qml->setContextProperty("cs", this);

    AbstractPane* root = qml->createRootNode<AbstractPane>();
    Application::setScene(root);
}

 

Now we can add our list to the qml. Put the list into a container so we can set an appropriate background. For simplicity, just use a background colour.  Import the custom library, and set the dataModel property on the ListView.  The data model returns strings (as Varargs), but we need to distinguish headers from items, so add the usual function in the ListView to treat indexPaths of length one as “header” and everything else as “item”.

 

Your main.qml file will now look something like:

import bb.cascades 1.0
import custom.lib 1.0

Page {
    content: Container {
        background: Color.create("#272727")
        layout: DockLayout {
        }
        
        ListView {
            layoutProperties: DockLayoutProperties {
                horizontalAlignment: HorizontalAlignment.Center
            }
            dataModel: HelloWorldDataModel {
            }
            
            // Treat outer level as header, inner level as item
            function itemType(data, indexPath) {
                if (indexPath.length == 1)
                {
                    return "header";
                }
                return "item";
            }
        }
    }
}

 

Run the App

You'll see a standard list view with headers "hello" and "world" with 2 and 3 children respectively:

helloworldlist.png

 

 

Source Code

The source code for this sample can be downloaded from the BlackBerry® project on Github® here:

https://github.com/blackberry/Cascades-Samples/tree/master/HelloWorldDataModel

Users Online
Currently online: 4 members 2,518 guests
Recent signins:
Please welcome our newest community members: