How to Create a Generic Object Model For Use in Qml

How to create a generic object model for use in QML?

//Is this possible in any way?
//Q_PROPERTY(AnimalModel modelAnimals READ modelAnimals NOTIFY modelAnimalsChanged)

Yes it is, didn't you try? Of course, it will not be a AnimalModel but a AnimalModel *, but as long as the model inherits QAbstractListModel, that's all you need. You don't even need the NOTIFY part, as changes, internal to the model will be automatically reflected anyway. modelAnimalsChanged only makes sense when you replace the entire model with a different model, and naturally, to shut up QML's warnings about using a property without a notify signal. A cleaner way to do the latter when the model object doesn't change is to just return a AnimalModel * from a slot or a Q_INVOKABLE.

If you want a truly flexible model, you can make one that stores QObject *, then from QML you can create arbitrary objects with arbitrary properties, and add to the model. Then from the model you have a single object role which returns the object, and you can query and use the object to retrieve the properties it holds. Whereas a "classical" list model implementation will define a model with a static, fixed schema, using this approach allows to have "amorphous" objects in the model with different properties.

Naturally, this requires some type safety, for example have a property int type for each object in such a model, and based on it you can determine the available properties for the object. My usual approach is to have a Loader for a delegate, and have it pass the object as a data source to different QML UI implementations visualizing that object type that it instantiates. This way you have both different objects in the model, and different QML items as view delegates.

The last step to making the ultimate "jack of all trades" list/model object is to implement QQmlListProperty and Q_CLASSINFO("DefaultProperty", "container") for it, allowing you to both compose the list/model dynamically, or using QML's declarative syntax. Also note that with this solution, you can add to or remove from such a model, even remove declaratively instantiated objects.

Also, depending on your usage scenario, you may have to either qmlRegisterType() or qmlRegisterUncreatableType() for the model.

OK, on a second glance, it looks like by "model of any data" you didn't mean schema-less models but simply different schema models. In that case, instead of returning an AnimalModel *, you can use a QAbstractListModel * or even a QObject * - it will work in QML anyway, as it employs dynamism through the meta system. But at any rate, schema-less models are that much more powerful and flexible, and they don't need C++ code to be defined, it can all work from QML alone.

class List : public QAbstractListModel {
Q_OBJECT
QList<QObject *> _data;

Q_PROPERTY(int size READ size NOTIFY sizeChanged)
Q_PROPERTY(QQmlListProperty<QObject> content READ content)
Q_PROPERTY(QObject * parent READ parent WRITE setParent)
Q_CLASSINFO("DefaultProperty", "content")
public:
List(QObject *parent = 0) : QAbstractListModel(parent) { }
int rowCount(const QModelIndex &p) const { Q_UNUSED(p) return _data.size(); }
QVariant data(const QModelIndex &index, int role) const {
Q_UNUSED(role)
return QVariant::fromValue(_data[index.row()]);
}
QHash<int, QByteArray> roleNames() const {
static QHash<int, QByteArray> roles = { { Qt::UserRole + 1, "object" } };
return roles;
}
int size() const { return _data.size(); }
QQmlListProperty<QObject> content() { return QQmlListProperty<QObject>(this, _data); }

public slots:
void add(QObject * o) { insert(o, _data.size()); }

void insert(QObject * o, int i) {
if (i < 0 || i > _data.size()) i = _data.size();
beginInsertRows(QModelIndex(), i, i);
_data.insert(i, o);
o->setParent(this);
sizeChanged();
endInsertRows();
}

QObject * take(int i) {
if ((i > -1) && (i < _data.size())) {
beginRemoveRows(QModelIndex(), i, i);
QObject * o = _data.takeAt(i);
o->setParent(0);
sizeChanged();
endRemoveRows();
return o;
} else qDebug() << "ERROR: take() failed - object out of bounds!";
return 0;
}

QObject * get(int i) {
if ((i > -1) && (i < _data.size())) return _data[i];
else qDebug() << "ERROR: get() failed - object out of bounds!";
return 0;
}

void internalChange(QObject * o) { // added to force sort/filter reevaluation
int i = _data.indexOf(o);
if (i == -1) {
qDebug() << "internal change failed, obj not found";
return;
} else {
dataChanged(index(i), index(i));
}
}

signals:
void sizeChanged();
};

Then, after you qmlRegisterType<List>("Core", 1, 0, "List"); you can use it pretty much any way you want to - it will hold any QObject or derived, naturally including QMLs QtObject It can directly be used as a model to drive a ListView. You can populate it dynamically using the slots or declarative, like this:

List {
QtObject { ... }
QtObject { ... }
List {
QtObject { ... }
QtObject { ... }
}
}

It will also handle object ownership, and you can easily nest it, producing in essence a compartmentalized tree model - note that you can't declaratively do that with QML's ListModel. You may want to add a parentChanged signal and implement a setter that emits it if you want to bind against a changing parent, it was not necessary in my case.

As of how to use it with a view, you can either use the objectName property or an int type property or basically any means to discern between different object types, and use a Loader for the delegate:

Loader {
// using component in order to capture context props and present to the variable delegate
sourceComponent: Qt.createComponent(obj.objectName + ".qml")
// if that is not needed simply use
// source: obj.objectName + ".qml"
// or setSource to pass specific properties to delegate properties
// Component.onCompleted: setSource(obj.objectName + ".qml", {/*prop list*/})
}

Update:

Here is also the gist of the implementation for a simple and just as dynamic and generic sorting and filtering proxy to go with this model for enhanced usability.

How to create a Generic ListModel for QML

I figured out, that it is not possible to use the Q_OBJECT-Macro in a class-template. That's why my GenericListModel consists of two part.

1. GenericListModelData

The first part is the Model itself that derives from QAbstractListModel and implements the basic functions data(), rowCount() and roleNames().

2. GenericListModel

The second part is the class-template that is used as a wrapper to provide functions similar to a QListView.

If you have any suggestions or questions please let me know. It would be really nice to improve this solution.

I uploaded the full sourcecode here:
https://github.com/sebabebibobu/QGenericListModel/

1. GenericListModelData

QVariant GenericListModelData::data(const QModelIndex &index, int role) const
{
QObject *item = m_itemList.at(index.row());
return item->property(item->metaObject()->property(role).name());
}

/*
* Returns the number of items attached to the list.
*/
int GenericListModelData::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_itemList.size();
}

/*
* Generates a hash out of QMetaObject property-index and property-name.
*/
QHash<int, QByteArray> GenericListModelData::roleNames() const
{
QHash<int, QByteArray> roles;

if (!m_itemList.isEmpty()) {
for(int i = 0; i < m_itemList.at(0)->metaObject()->propertyCount(); i++) {
roles[i] = m_itemList.at(0)->metaObject()->property(i).name();
}
}

return roles;
}

/*
* Append Item to List.
*/
void GenericListModelData::appendItem(QObject *item)
{
/* map the notify()-signal-index with the property-index when the first item get's inserted */
if (m_itemList.isEmpty()) {
for(int i = 0; i < item->metaObject()->propertyCount(); i++) {
m_propertySignalIndexHash.insert(item->metaObject()->property(i).notifySignalIndex(), i);
}
}

/* connect each notify()-signals to the onDataChanged()-slot which call's the dataChanged()-signal */
for(int i = 0; i < item->metaObject()->propertyCount(); i++) {
connect(item, "2" + item->metaObject()->property(i).notifySignal().methodSignature(), this, SLOT(onDataChanged()));
}

/* finally append the item the list */
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_itemList.append(item);
endInsertRows();
}

/*
* Helper-Slot that emit's the dataChanged()-signal of QAbstractListModel.
*/
void GenericListModelData::onDataChanged()
{
QModelIndex index = createIndex(m_itemList.indexOf(sender()),0);

QVector<int> roles;
roles.append(m_propertySignalIndexHash.value(senderSignalIndex()));
emit dataChanged(index, index, roles);
}

2. GenericListModel

template <typename T>
class GenericListModel : public GenericListModelData
{
public:
explicit GenericListModel(QObject *parent) : GenericListModelData(parent) {

}

void append(T *item) {
appendItem(item);
}

T *at(int i) {
return qobject_cast<T *>(m_itemList.at(i));
}
};

Update 01.05.2016

GrecKo posted in the comments, that a project like mine already exists. That's why I decided to share the link of this project here too:

http://gitlab.unique-conception.org/qt-qml-tricks/qt-qml-models

QT qml model within a model? and accesible via qml

Yes its fine.

You need to return a pointer to your sub model object wrapped in a variant,

QVariant::fromValue(&subModel) 

You probably also need to register your model pointer with Metatype system using

qRegisterMetaType<MySubModelClass*>("MySubModelClass*" );

QML C++ integration: View Models for dynamic classes containing arrays of other classes

I am now finished with the project and I have accomplished all the goals I had and overcome the problems that I have asked for help here and a few other ones that showed up later on.

For those who might find this page while searching for solutions for similar problems they have, you can find all the source code for my complete project on my Github page here: https://github.com/Nizars/PokeApp

I would like to point out a few changes that I have made:

I have switched to an SQL method of storing the cards, the albums and the card images as well.

I have used different models for different views. A proxy model for relational queries used by the table view and two sql models for other functionalities.

I also use a custom made image provider for checking for images requested from the qml side in the database first before requesting the images from an API through https and storing it in the database if it wasn't found there at first.

I have also switched to a borderless window to get rid of the windows default window border and created my own close, minimize and maximize buttons. I have also used a mouse position provider to make dragging the window around possible and seamless.

I have also added a working RSS feed to the homepage and used a page swipe view for the different pages in the app, I used connections to signal messages between the different qml documents so that data can be sent between them.

Final note: You need to include your own OpenSSL library includes for the network requests to work and create the sql tables in your local server as well as connect the app to it in pokeapp.cpp.

Feel free to ask me any questions if you need any help.

Integrating your own QT C++ data model with QML

Providing the roles in the model are properly implemented, all you need to do is:

Text { text: "Email: " + email}.

If that doesn't work, then definitely you have a problem with your model implementation.

As GrecKo noted in the comments, the format model.role is actually possible, but not really necessary unless you have name conflicts.

How to make some reusable QML object, which can inject another QML object?

The answer I linked works like this:

Main.qml

Card {
titleText: "Image Viewer"
innerObject: Rectangle {
Component.onCompleted: {
console.log(parent.objectName)
}
}
}

Card.qml

Rectangle {
property string titleText: "[Hello Untitled Title]"

default property alias innerObject : innercolumn.children


id: root
color: "#fff"
ColumnLayout {
id: innercolumn
objectName: "column"
anchors.fill: parent
Rectangle {
id: header
height: 10
width: parent.width
color: "#666"
RowLayout {
Text { text: titleText; color: "#fff" }
}
}
}
}


Related Topics



Leave a reply



Submit