1

I'm trying to make an example to make the XML dataModel change dinamically in my .qml file (when I click the button) using C++. For that, I'm returning a Qt property (GroupDataModel). But after returning the object, the ListView doesn't change, though I see the model property is returned again.

OBS: If I load it from a XMLDataModel in the .qml, instead of loading in C++ code, it works.

This is my XmlTest.hpp:

#ifndef XmlTest_HPP_
#define XmlTest_HPP_

#include <QObject>
#include <bb/cascades/GroupDataModel>

namespace bb { namespace cascades { class Application; }}

class XmlTest : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bb::cascades::GroupDataModel* model READ model NOTIFY onModelChanged);
public:
    XmlTest(bb::cascades::Application *app);
    virtual ~XmlTest() {}

    Q_INVOKABLE
    bb::cascades::GroupDataModel *model();

    Q_INVOKABLE
    void setGroupDataModel();
signals:
    void onModelChanged();
private:
    bb::cascades::GroupDataModel *m_model;
};

#endif /* XmlTest_HPP_ */

and XmlTest.cpp:

#include "XmlTest.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
#include <bb/data/XmlDataAccess>

using namespace bb::cascades;
using namespace bb::data;

XmlTest::XmlTest(Application *app)
: QObject(app)
{
    m_model = new GroupDataModel();
    qRegisterMetaType<GroupDataModel *>("GroupDataModel *");

    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
    qml->setContextProperty("_xmlTest", this);

    AbstractPane *root = qml->createRootObject<AbstractPane>();
    app->setScene(root);
}

GroupDataModel *XmlTest::model()
{
    qDebug("Returning m_model");
    return m_model;
}

void XmlTest::setGroupDataModel()
{
    XmlDataAccess xml;
    QVariant xmlData = xml.load(QDir::currentPath() + "/app/native/assets/models/model.xml");
    m_model->clear();
    m_model->insertList(xmlData.toList());
    qDebug("File loaded");
    emit this->onModelChanged();
}

My main.qml file (just a ListView with a Button):

import bb.cascades 1.0

Page {
    Container {
        id: mainContainer
        layout: DockLayout {}
        ListView {
            id: listView
            dataModel: _xmlTest.model
            //dataModel: XmlDataModel {
            //    source: "models/model2.xml"
            //}
            onDataModelChanged: {
                console.log("Data model changed!"); 
            }
            listItemComponents: [
                ListItemComponent {
                    type: "user"
                    StandardListItem {
                        title: ListItemData.realname
                        description: ListItemData.name
                    }
                },
                ListItemComponent {
                    type: "option"
                    StandardListItem {
                        title: ListItemData.title
                    }
                }
            ]
        }
        Button {
            text: "Click"
            onClicked: {
                console.log("Trying to load file");
                _xmlTest.setGroupDataModel();
            }
            verticalAlignment: VerticalAlignment.Bottom
            horizontalAlignment: HorizontalAlignment.Center
        }
    }
}

and the XML I'm trying to load:

<root>
    <user name="myUsername" realname="My Real Name"/>
    <option title="Option 1"/>
    <option title="Option 2"/>
    <option title="Option 3"/>
    <option title="Option 4"/>
    <option title="Option 5"/>
</root>
Dielson Sales
  • 1,715
  • 1
  • 20
  • 25

3 Answers3

1

Q_INVOKABLE functions are some kind of slots, so they cannot return an object. You should find the ListView and put the data Model.

  • Add in the QML after id: objectName: "listview"
  • Get the listView in C++: ListView* listView = root->findChild("listView");
  • set the DataModel: listView->setDataModel(m_model);
Benoit
  • 1,922
  • 16
  • 25
  • 1
    Q_INVOKABLE's are not slots, they are just methods that are exposed to QML. They can return values. – craigmj Jan 16 '13 at 08:55
1

You only need your setGroupDataModel() to be Q_INVOKABLE - the others are just property accessors.

But you're misunderstanding the GroupDataModel. You don't want to emit a onModelChanged() unless the model PROPERTY changes. In your case, the PROPERTY hasn't changed, all that has changed is the values in the property. So you don't need the emit onModelChanged(), which should have the new value as a parameter, I think.

So where is the problem?

You need to check that you're reading the data from your XML file properly.

Then you need to read up on the GroupDataModel - if I'm reading it properly, it will only use 'item' and 'header' as the item type, so you ListItemComponents aren't going to be used.

It's a little easier (and will work ;-) if instead of using a GroupDataModel you use an XmlDataModel.

Here's my XmlListView.hpp main class (your XmlTest class):

// Default empty project template
#ifndef XmlListView_HPP_
#define XmlListView_HPP_

#include <QObject>
#include <bb/cascades/XmlDataModel>
#include <bb/cascades/DataModel>

namespace bb { namespace cascades { class Application; }}

class XmlListView : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bb::cascades::DataModel *model READ model NOTIFY onModelChanged);
public:
    XmlListView(bb::cascades::Application *app);
    virtual ~XmlListView() {}

    bb::cascades::DataModel *model();
    Q_INVOKABLE void setGroupDataModel();

signals:
    void onModelChanged();
private:
    bb::cascades::XmlDataModel *m_model;
};


#endif /* XmlListView_HPP_ */

And the .cpp:

// Default empty project template
#include "XmlListView.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>

#include <bb/data/XmlDataAccess>

using namespace bb::cascades;
using namespace bb::data;

XmlListView::XmlListView(bb::cascades::Application *app)
: QObject(app)
{
    m_model = new XmlDataModel();

    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
    qml->setContextProperty("_xmlTest", this);

    AbstractPane *root = qml->createRootObject<AbstractPane>();
    app->setScene(root);
}

DataModel *
XmlListView::model() {
    return m_model;
}

void
XmlListView::setGroupDataModel() {
    m_model->setSource(QUrl("models/model.xml"));
}

My main.qml, like yours:

import bb.cascades 1.0

Page {
    Container {
        id: mainContainer
        layout: DockLayout {}
        ListView {
            id: listView
            dataModel: _xmlTest.model
            listItemComponents: [
                ListItemComponent {
                    type: "user"
                    StandardListItem {
                        title: ListItemData.realname
                        description: ListItemData.name
                    }
                },
                ListItemComponent {
                    type: "option"
                    StandardListItem {
                        title: ListItemData.title
                    }
                }
            ]

        }
        Button {
            text: "Click"
            onClicked: {
                _xmlTest.setGroupDataModel()
            }
            verticalAlignment: VerticalAlignment.Bottom
            horizontalAlignment: HorizontalAlignment.Center
        }

    }
}

And the .xml model file is the same as yours.

To really understand where you were going wrong, you need to examin ehte data loading from the XmlDataAccess class. I've still not quite worked it out - and I'm afraid I've got no more time right now - but I tried, in my constructor:

qDebug() << "==========================================================";
XmlDataAccess xml;
QVariant xmlData = xml.load(QDir::currentPath() + "/app/native/assets/models/model.xml");
if (xml.hasError()) {
    qDebug(xml.error().errorMessage().toAscii());
} else {
    QVariantList list = xmlData.toList();
    qDebug() << "list len = " << list.size();
}
qDebug() << "==========================================================";

Watching my debug log, I see that the list len = 0, so this code isn't successfully reading anything from the XML. As I say, I've not yet worked out how to get it working. Homework assignment? (please post when you work it out)

All the best, C

craigmj
  • 4,827
  • 2
  • 18
  • 22
  • You're right, the problem is in the way that the data is parsed when I call .toList(). Somehow, a file that works when we use XMLDataModel in QML doesn't work when I parse it from C++ code. But I haven't still figured out how to make it work with a header and an item... – Dielson Sales Jan 17 '13 at 04:22
  • If you use the XMLDataModel, then you can differentiate the "user" and the "options", though perhaps that isn't exactly what you mean...? Are you looking to have the 'user' nodes as an indexPath of [n], and the options an indexPath of [n, o]? – craigmj Jan 17 '13 at 07:24
  • It doesn't matter really. What I wanted was to differentiate the 'user' as a header and the 'option' as a standard item. I'm using GroupDataModel, cause when I tried XmlDataModel, it didn't appear in the screen, but I can only make it work if I use a QVariantList, where there's no way to know the name of the node. – Dielson Sales Jan 19 '13 at 16:49
0

If what you really want is to display the 'user' data as a header, then you can use the exact code suggested by @craigmj in his answer to get your data into the model, but just change the definition of the ListItemComponent in your main.qml to use Header instead of StandardListItem. I've provided the entire ListView below, which you should be able to just drop into your main.qml. Header is documented here.

    ListView {
        id: listView
        dataModel: _xmlTest.model

        onDataModelChanged: {
            console.log("Data model changed!");
        }
        listItemComponents: [
            ListItemComponent {
                type: "user"
                Header {
                    title: ListItemData.realname
                    subtitle: ListItemData.name
                }
            },
            ListItemComponent {
                type: "option"
                StandardListItem {
                    title: ListItemData.title
                }
            }
        ]
    }
nonesuchnick
  • 627
  • 4
  • 17