[SOLVED] Implementing a QAbstractListModel on top of a QHash for use in QML



  • This is the first time I am writing a custom model and quite frankly I got stuck. I implemented the basic stuff, and managed to get a working ListView in QML, but so far my model is pretty much read only. I can get data into the QHash and display it, but I am a little unclear on how to make my model dynamic.

    The model itself is quite simple - the hash contains a QString key and a uint value. When a string is "inserted" it checks if the entry already exists, if not - then it is added, if yes - then its value just gets incremented, respectively adding a row and updating a row.

    Here is what I have so far, I'd appreciate a few pointers on implementing a working insert() method that actually updates the model, because currently it just adds to the hash, but the model isn't updating, and also with sorting by key or value:

    @class TestModel : public QAbstractListModel
    {
    Q_OBJECT
    public:
    TestModel(QObject * parent = 0) : QAbstractListModel(parent) {}

    enum Roles {
        Word = Qt::UserRole,
        Count,
        EvenOrOdd,
        Index
    };
    
    QHash<int, QByteArray> roleNames() const {
        QHash<int, QByteArray> roles;
        roles[Word] = "word";
        roles[Count] = "count";
        roles[Index] = "index";
        return roles;
    }
    
    int rowCount(const QModelIndex &parent) const {
        Q_UNUSED(parent)
        return m_data.size();
    }
    
    QVariant data(const QModelIndex &index, int role) const {
        if (!index.isValid()) return QVariant();
    
        QHash<QString, uint>::const_iterator iter = m_data.constBegin() + index.row();
    
        switch (role) {
        case Word:
            return iter.key();
        case Count:
            return QString::number(iter.value());
        case Index:
            return index.row();
        }
    
        return QVariant();
    }
    
    Q_INVOKABLE void insert(const QString &key) {
        if (m_data.contains(key)) {
            m_data.insert(key, m_data.value(key) + 1);
        } else {
            int size = m_data.size();
            beginInsertRows(QModelIndex(), size, size);
            m_data.insert(key, 1);
            endInsertRows();
        }
    }
    

    private:
    QHash<QString, uint> m_data;
    };@


  • Lifetime Qt Champion

    Hi,

    Since your data has been modified, you should emit "dataChanged":http://qt-project.org/doc/qt-4.8/qabstractitemmodel.html#dataChanged

    Hope it helps



  • Hi,

    Since your model is editable, shouldn't you also implement setData();



  • [quote author="SGaist" date="1366269525"]Hi,

    Since your data has been modified, you should emit "dataChanged":http://qt-project.org/doc/qt-4.8/qabstractitemmodel.html#dataChanged

    Hope it helps[/quote]

    I never really reached that part, I got stuck on inserting rows, tried emitting rowsInserted(), but .. I couldn't. Here is the signature of rowsInserted():
    @
    void QAbstractItemModel::rowsInserted(const QModelIndex & parent, int start, int end) [signal]@

    However, for me, it also requires a QPrivateSignal as the final parameter, and unlike all the other cases where omitting it is OK, in this case I wasn't able to emit the signal, with or without it, the compiler kept compiling and refusing to compile.


  • Lifetime Qt Champion

    It's done for you (through the use of begin/end insertRows, and as the doc says, you can't emit it from subclasses.



  • In that case, why doesn't this insert a row?

    @int size = m_data.size();
    beginInsertRows(QModelIndex(), size, size);
    m_data.insert(key, 1);
    endInsertRows();@

    It only "works" before the model is displayed, e.g. it only inserts data into the hash, but the changes are not reflected in the view.


  • Lifetime Qt Champion

    Did you also check if that doesn't work with a QListView ?



  • Haven't really tested since the requirement is for a QML application. Are there any known limitations of using QML list view with QAbstractListModel?



  • I don't have much clue about QML, but I think the problem is your custom roles. If you try your code with standard roles, say Qt::DisplayRole, then the hash items appear in views so you probably have to match your custom roles with the data you want them to display. I don't have any experience with custom roles, but according to the docs it seems you have to reimplement "itemData()":http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#itemData" and/or "setItemData()":http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#setItemData.



  • Finally found the spare time to finish it, haven't tested it in detail but "it looks like it's working", there were some minor issues with finding the position of updated or inserted rows and there were in fact some QML related limitations. I can't say the whole ordeal was very intuitive.

    @typedef QHash<QString, uint> Data;

    class NewModel : public QAbstractListModel {
    Q_OBJECT
    Q_PROPERTY(int count READ count NOTIFY countChanged)

    public:
    NewModel(QObject * parent = 0) : QAbstractListModel(parent) {}

    enum Roles {WordRole = Qt::UserRole, CountRole};
    
    QHash<int, QByteArray> roleNames() const {
        QHash<int, QByteArray> roles;
        roles[WordRole] = "word";
        roles[CountRole] = "count";
        return roles;
    }
    
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
        if (index.row() < 0 || index.row() >= m_data.size()) return QVariant();
        Data::const_iterator iter = m_data.constBegin() + index.row();
    
        switch (role) {
        case WordRole:
            return iter.key();
        case CountRole:
            return iter.value();
        }
        return QVariant();
    }
    
    int rowCount(const QModelIndex &parent) const {
        Q_UNUSED(parent)
        return m_data.size();
    }
    
    int count() const { return m_data.size(); }
    

    public slots:
    void append(const QString &word) {
    bool alreadyThere = m_data.contains(word);
    if (alreadyThere) m_data[word]++;
    else m_data.insert(word, 1);

        Data::const_iterator iter = m_data.find(word);
        uint position = delta(iter);
    
        if (alreadyThere) {
            QModelIndex index = createIndex(position, 0);
            emit dataChanged(index, index);
        } else {
            beginInsertRows(QModelIndex(), position, position);
            endInsertRows();
            emit countChanged();
        }
    }
    

    signals:
    void countChanged();

    private:
    uint delta(Data::const_iterator i) {
    uint d = 0;
    while (i != m_data.constBegin()) { ++d; --i; }
    return d;
    }

    Data m_data;
    

    };@

    The next challenge is sorting by roles, any ideas? QSortFilterProxyModel doesn't seem to work.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.