1
votes

How can I return a custom QObject sub-class from QAbstractListModel and use it in a QML ListView.

I tried to return the objects as display role and I use in my qml display.property to access properties, it works fine but I saw on some posts people using model as the qobject from qml and accessing properties as model.property. Am I missing something?.

Another question: If I want to expose the object at the ListView level and using it to set some other panel like a master-view detail is exposing the role (in my case display) as a variant property in the delegate and setting it at the listview level with the onCurrentItemChanged signal is the correct way to do it ??

this is what I am trying but it does not work:

#ifndef NOTE_H
#define NOTE_H

#include <QObject>

class Note : public QObject
{
    Q_OBJECT

    Q_PROPERTY(QString note READ note WRITE setNote NOTIFY noteChanged)
    Q_PROPERTY(int id READ id WRITE setId NOTIFY idChanged)

    QString m_note;
    int m_id;

public:
    explicit Note(QObject *parent = 0);
    Note(QString note, int id, QObject *parent = 0);

    QString note() const
    {
        return m_note;
    }

    int id() const
    {
        return m_id;
    }

signals:

    void noteChanged(QString note);

    void idChanged(int id);

public slots:
    void setNote(QString note)
    {
        if (m_note == note)
            return;

        m_note = note;
        emit noteChanged(note);
    }
    void setId(int id)
    {
        if (m_id == id)
            return;

        m_id = id;
        emit idChanged(id);
    }
};

#endif // NOTE_H

the view model:

#ifndef NOTESVIEWMODEL_H
#define NOTESVIEWMODEL_H

#include <QAbstractListModel>
#include <QVector>
#include "note.h"

class NotesViewModel : public QAbstractListModel
{
    Q_OBJECT

    QVector<Note*> notes;


public:
    NotesViewModel();

    QVariant data(const QModelIndex &index, int role) const override;
    int rowCount(const QModelIndex &parent) const override;

};

#endif // NOTESVIEWMODEL_H

implementation of the view model:

NotesViewModel::NotesViewModel()
{
    notes.append(new Note("note 1", 1));
    notes.append(new Note("note 2", 2));
    notes.append(new Note("note 3", 3));
    notes.append(new Note("note 4", 4));
    notes.append(new Note("note 5", 5));
}

QVariant NotesViewModel::data(const QModelIndex &index, int role) const
{
    qDebug() << "fetching data : " << index.row();
    if(!index.isValid()) return QVariant();
    if(index.row() >= 5) return QVariant();
    if(role == Qt::DisplayRole)
        return QVariant::fromValue(notes[index.row()]);
    return QVariant();
}

int NotesViewModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return notes.count();
}
2
In general a model does not expose it's inner objects but only things like display role etc. you can though add custom roles and return things via the QVariant on data(...). Like an identifier, to get the object somewhere else.Hayt
What you are saying is correct and I agree with you, but in situation like a masterdetail view where the detail view is not part of the master view delegate it's very hard to do.Houss_gc
it's kind of hard to advise without further details. You can have a map somewhere with the Notes and an ID. When you then give the ID back as custom you can just locate the note in the map by id.Hayt
I am actually doing this using some database and the record Id, and sending the Id to other pages using signals which I find a bit confusing when there is many pages and many signals but in case of the hole object accessible from the listview level it will be very simple to access the data.Houss_gc
I am not sure if those would not make this question too broad for stackoverflow.Hayt

2 Answers

2
votes

Since you've already answered your second question, let me take on the first one, i.e. display.propertyName vs model.propertyName

Basically the first one is just a short hand for model.display.propertyName, i.e. the "display" property of the model's data at the given index is being accessed. In your case that returns an object, which has properties on its on.

The model.propertyName could also be written as just propertyName, meaning the model's data() method is called with "role" being the numerical equivalent for "propertyName".

This mapping from "propertyName" to its numerical equivalent is done with the QAbstractItemModel::roleNames() method. Its default implementation has some base mappings such as mapping "display" to Qt::DisplayRole.

1
votes

I played a little with the qml ListView trying to figure out how to expose the currentItem data to the outside world. This is how I managed to do it, maybe it will be of use for newbies like me.

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    ListModel {
        id: modell
        ListElement {
            fname: "houssem"
            age: 26
        }
        ListElement {
            fname: "Anna"
            age: 26
        }
        ListElement {
            fname: "Nicole"
            age: 26
        }
        ListElement {
            fname: "Adam"
            age: 27
        }
    }

    ListView {
        id: lv
        height: 100
        width: 200
        clip: true
        model: modell
        property string selectedName: currentItem.name
        delegate: Component {
            Item {
                id: mainItem
                width: ListView.view.width
                height: 80
                property string name: fname
                Text {
                    text: "name " + fname + " age " + age
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: mainItem.ListView.view.currentIndex = index
                }
            }
        }
    }
    Text {
        anchors.right: parent.right
        text: lv.selectedName
    }
}

As you can see in the code, to expose the data of currentItem I had just to declare properties in the delegate Item (name property of type string in the present example) and then bind a property on ListView (selectedName in this example) to the currentItem.property, this way the property on the ListView will be updated automatically when I select other items from the list and I will have access to this items form other Items of the UI.