BB10 Cascades: Populate a DropDown from an XML File


A question was asked about how to populate a Cascades DropDown control from an XML File. Since I've populated DropDowns from SQLite data I decided to try my hand at pulling from a different datasource. The question was spawned from a discussion about creating a custom control.

A Cascades sample project is available for download at the bottom of the page.

Note: I don't have a good code highlighting plugin so the inline code samples don't look that great.

References

 

Overview

To populate a DropDown from an XML File you will need to do the following things:

  • Create a C++ Class that can read
  • Make an instance of the class and bind it so it can be accessed from QML Pages
  • Create a QML Page that has
    • DropDown control with an id
    • attachedObjects with a definition for Option{}
    • Event Binding to load the DropDown control

 

C++ XML Reader Class

The first thing you need to do is create a class that uses QT's QXmlStreamReader class to read your desired XML data into a QList. There is plenty of documentation available for the QXmlStreamReader, but I'd recommend taking a look at this comprehensive sample application.

I ended up with a class file that looks like this:

 

const QString XmlBasePath = QDir::currentPath() + "/app/native/assets/XML/";   // Base path to the XML folder in your app 
QVariantList XmlReader::LoadXML(QString xmlPath, QString rowType, QString attribute){

    // Setup the full path to the XML file // And open a QFile for use by the XmlStreamReader
    QString XmlPath = XmlBasePath + xmlPath;
    QFile* XFile = new QFile(XmlPath);
    XFile->open(QIODevice::ReadOnly | QIODevice::Text);

    // Initialize the XML reader with the XML file
    QXmlStreamReader Xml(XFile);

    // // This is where the magic happens: Read the Attributes from each entry into a QList
    QList< QVariantMap > Entries;
    while(!Xml.atEnd()){
        QXmlStreamReader::TokenType Token = Xml.readNext();

        if(Token == QXmlStreamReader::StartElement){
            if(Xml.name() == rowType) {
                // If this is an xml element that is named correctly, try to read the attributes
                QXmlStreamAttributes attributes = Xml.attributes();

                QVariantMap Entry;
                Entry[attribute] = attributes.value(attribute).toString(); // pull off the specified attribute
                Entries.append(Entry);
            }
        }
    }

    // Cleanup file handle
    XFile->close();

    // Debug view. Look at slog2info to see what this 'looks' like
    qDebug() << "stuff coming" << Entries;

    // Convert to QVariantList for transfer to QML
    QVariantList QVList;
    for(int i = 0; i < Entries.length(); i++){
        QVariantMap map;
        QVList << Entries[i];
    }

    return QVList;
}

 

Configure Cascades to let QML use the Class

Once you have your class, you'll need to instantiate and bind an instance of it for use by QML. That is pretty straight-forward and only involves adding a few lines to your applicationName.cpp file. In this case the sample application that I used to create this is named DropDownFromXmlData. Lines to add:

  • #include "XmlReader.h"                       // This is the name of my XML reader class (top of file)
  • XmlReader *xml = new XmlReader();    // Create and instance and bind to QML
  • qml->setContextProperty("XML", xml); // Add this after your QmlDocument declaration

 

Complete code listing of DropDownFromXmlData.app. The relevant lines have been Bolded:

 
#include "DropDownFromXmlData.hpp"
#include "XmlReader.h"

#include
#include
#include

using namespace bb::cascades;

DropDownFromXmlData::DropDownFromXmlData(bb::cascades::Application *app)
: QObject(app)
{
 // create scene document from main.qml asset // set parent to created document to ensure it exists for the whole application lifetime
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

 // Make an instance of the XML Reader class so it is available to QML pages 
XmlReader *xml = new XmlReader(); qml->setContextProperty("XML", xml); // create root object for the UI
AbstractPane *root = qml->createRootObject(); // set created root object as a scene app->setScene(root); }

 

 

Create a QML page that can use the Class

Now for the fun part: Creating a QML page that calls the XMLReader and populates the dropdown. Here is the main.qml file from this sample project. The relevant lines have been Bolded:

import bb.cascades 1.0
Page {
    onCreationCompleted: {
        // Call the C++ Method to load the XML data. 3 arguments: XML File, Row name, attribute name
        var xmlContents = XML.LoadXML("Information.xml", "object", "description");

        // Clear the contents of the DropDown
        exampleDropDown.removeAll();

        // Add the options from the XML to the DropDown
        for(var x = 0; x < xmlContents.length; x++){ 
console.log("entry: " + x);
console.log(xmlContents[x].description);
var opt = option.createObject();
//
opt.text = xmlContents[x].description; // Needs to be an 'Option' before the DropDown will accept it
exampleDropDown.add(opt);
}
} Container { layout: StackLayout {} Label { text: qsTr("Hello XML!") textStyle.base: SystemDefaults.TextStyles.BigText horizontalAlignment: HorizontalAlignment.Center } Divider { } DropDown { // id: exampleDropDown // DropDown that we'll add options to } // } //
// BBTEN-302: I need to specify a new ComponentDefinition in the attachedObjects section
// In order to add options to a DropDown
// https://7thzero.com/blog/blackberry-10-cascades-create-a-custom-control-dropdownlist/
//
attachedObjects: [
ComponentDefinition {
id: option Option{}
}
]
}

 

Sample code download

Since I only show the highlights above, I've created a working Cascades application that implements what I've discussed above and made it available for download here. This was built with the BB10 Gold SDK and works on my Dev Alpha device. Should also work on the simulator

Enjoy!