Using QAbstractListModel in ListView

MichaelCMS picture MichaelCMS · Dec 10, 2014 · Viewed 15.4k times · Source

I'm new with Qt , so please bear with me .

I've successfully managed to populate a ListView from a StringList and a QList of Object*

What I'm struggling now with is to populate a ListView in QML using a class defined in C++ that derives QAbstractListModel.

Here's the prototype of my CPP class :

class MessageListEntryModel : public QAbstractListModel
{

Q_OBJECT
public:

enum eMLERoleTypes
{
    MLERT_MSG = Qt::UserRole+1,
    MLERT_COLOR
};

                                MessageListEntryModel(QObject* parent=0);
        virtual                 ~MessageListEntryModel();

        void                    AddEntry(QString aMessage, QColor aColor);

        // pure virtuals implementations
        QVariant                data(const QModelIndex &index, int role = Qt::DisplayRole) const;
        int                     rowCount(const QModelIndex &parent = QModelIndex()) const ;
        int                     columnCount(const QModelIndex &parent = QModelIndex()) const ;
        QModelIndex             index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
        QModelIndex             parent(const QModelIndex &child) const ;

        QHash<int,QByteArray>   roleNames();
private:
 QList<MessageEntry*> m_vpMessages;

MessageEntry is a simple class that contains 2 memebers , a QColor and a QString (the class doesn't extend QObject).

I had to implement all the above functions since they're pure virtual in an underlying class (is this normal? so far in tutorials/samples people mentioned only about roleNames and data).

The implementation of roleNames and data are as following :

QHash<int,QByteArray>  MessageListEntryModel::roleNames()
{
    QHash<int,QByteArray> rez;
    rez[MLERT_MSG]="message";
    rez[MLERT_COLOR]="messagecolor";
    return rez;
}

QVariant MessageListEntryModel::data(const QModelIndex &index, int role) const
{
    qDebug()<<" Data asked for "<<index.row()<<" and role "<<role;
    if (index.row()<0 || index.row()>=m_vpMessages.size())
    {
        return QVariant();
    }
    MessageEntry* entry = m_vpMessages[index.row()];
    if (role == MLERT_MSG)
    {
        return QVariant::fromValue(entry->message);
    } else if (role == MLERT_COLOR)
    {
        return QVariant::fromValue(entry->messageColor);
    }
    // should be unreachable code
    return QVariant();
}

The QML portion of the List View is something like this :

 ListView {
    id: quickMessageListdata
    model: quickListModel
    delegate: Rectangle {

        width: 400
        height: 25
        color:"#000000"
        Text{
            text: model.message
            color: model.messagecolor
        }
    }

So far this is my understanding on how to implement things in CPP and QML. For linking these two, I use the following code :

MessageListEntryModel* model =new MessageListEntryModel();
// Add various entries
...
// assign model in QML
m_pViewRef->rootContext()->setContextProperty("quickListModel",model);

With the code above, when running nothing is displayed in the ListView and I'm getting the following errors :

Unable to assign [undefined] to QString

Unable to assign [undefined] to QColor

I'm also registering the model class to be exported to QML (don't know if this is necessary) :

qmlRegisterType<MessageListEntryModel> ("dlti.exported",1,0,"MessageListEntryModel");

So it's quite obvious that either I missuderstood the proper use of a QAbstractListItem derived class OR I miss a simple vital key information.

I would appreciate some pointers to some relevant samples / tutorials (one that also shows you how to properly access data from the model in QML, since I've noticed that in CPP it never passes through the data function).

Also please notice that I'm using qt5 , so qt4.8 samples won't do the trick.

EDIT

After long hours of frustrations, I finally managed what was wrong with the damn thing :

My roleNames function signature was wrong ! The correct signature for overload is :

protected :
      QHash<int,QByteArray> roleNames() const;

Please notice the protected and the const modifiers.

After declaring the function the correct way , it all worked fine.

For further notice, implementing data and rowCount was sufficient :).

Thanks for the help. I will accept BaCaRoZzo's answer since I only manged to figure this out after looking over the code from the example.

As a side note, it works well with both message and model.message.

Answer

BaCaRoZzo picture BaCaRoZzo · Dec 11, 2014

How do you implement the addition method? You should use a method like in the example provided in my comment.

From the docs:

An insertRows() implementation must call beginInsertRows() before inserting new rows into the data structure, and it must call endInsertRows() immediately afterwards.

You should have something like:

void MessageListEntryModel::add(params...)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());   // kindly provided by superclass
    // object creation based on params...
    m_vpMessages << objectCreated;
    endInsertRows();                                          // kindly provided by superclass
} 

with

int MessageListEntryModel::rowCount(const QModelIndex & parent) const {
    Q_UNUSED(parent);
    return m_vpMessages.count();
}

Also, @Jonathan Mee comment is correct: use just the role names inside the delegate as you defined them in the model. If everything else is correct that is the way to access data.


One of the best documentation piece to understand C++ models implementation and usage is the Model subclassing reference. In this document is clearly depicted which are the most important methods to subclass and for what purposes.

As for the methods to implement, it really depends on the needs. Depending on the possible actions on the model, different methods should be implemented (refer to the above link for full details). A model for a ListView in which items can be added/removed can inherit from QAbstractListModel and just rely on the default implementations for most of the functions. You just need data(), roleNames() and rowCount(), as you already saw in most examples.

If instead you also need to edit data not just adding/removing it, then you also need other functions, particularly setData(). It is also your duty to notify the attached view(s) with any modification of the model occurred in setData(), via the signal dataChanged(). Refer again to the above provide subclassing reference.

Note also that, if the add method is modified with the Q_INVOKABLE modifier, i.e. it is declared as

Q_INVOKABLE void add(params...);

in the model header, you can also call it from QML (since the model is set as a context property) and you can write, for instance:

ListView {
    id: quickMessageListdata
    model: quickListModel
    delegate: Rectangle {

        width: 400
        height: 25
        color:"#000000"
        Text{
            text: model.message
            color: model.messagecolor
        }
    }

    Component.onCompleted: {
        quickListModel.add(params)
        quickListModel.add(params)      
    }
}

to insert items in the view as soon as the view is created. Clearly the same approach can be applied to other QML signals so that you can react to QML events and trigger addition/removal behaviours.

Finally, you don't need to register the model with qmlRegisterType. For your current requirement it is superfluous.