Search code examples
c++qtqmlqtquick2qtquickcontrols

Qml Listview items disappear when scrolling


I have got the following scrollview with listview inside:

ScrollView{
    anchors.fill: parent
    ListView{
        id: lvCommitsBranch
        model: git.getCommitsBranch();
        clip: true
        delegate: Rectangle {
            height: 100
            width: parent.width
            Text {
                anchors.left: parent.left
                font.bold: true
                text:model.author
                id:txtName
            }
            Text{
                anchors.left: parent.left
                anchors.top:txtName.bottom
                font.pixelSize: 10
                text:model.email
                id: txtEmail
            }
            Text {
                anchors.left: parent.left
                anchors.top:txtEmail.bottom
                text: model.message + ' ' + model.hash
                id: txtMsg
            }
            MouseArea{
                anchors.fill: parent
                onClicked: {
                    lvCommitsBranch.currentIndex = index;
                    console.log('Msg: ' + model.message);
                    console.log('Hash: ' + model.hash);
                }
                acceptedButtons: Qt.LeftButton | Qt.RightButton
            }
        }
    }
}

The issue is that when I scroll some items disappear (each time randomly and sometimes I have to scroll fast but not always).

enter image description here

When I click on the items that have not disappeared, I get undefined on all the model's properties. When Mousearea's onclick is triggered it prints the following:

qml: Msg: undefined

qml: Hash: undefined

I get the model info from a method (QAbstractListModel) that is returned from my git custom component.

This is my QAbstractListModel:

header:

class CommitsBranch : public QAbstractListModel
{
    Q_OBJECT
public:
    enum Roles {
        AuthorRole,
        EMailRole,
        MsgRole,
        DateRole,
        HashRole
    };
    explicit CommitsBranch(QObject *parent = 0);
    CommitsBranch(Repository *repo);
public:
    virtual int rowCount(const QModelIndex &parent) const override;
    virtual QVariant data(const QModelIndex &index, int role) const override;
protected:
    // return the roles mapping to be used by QML
    virtual QHash<int, QByteArray> roleNames() const override;
private:
    QList<Commit> m_data;
    QHash<int, QByteArray> m_roleNames;

};

Cpp:

CommitsBranch::CommitsBranch(QObject *parent)
    : QAbstractListModel(parent)
{
}

CommitsBranch::CommitsBranch(Repository *repo)
{
    m_roleNames[AuthorRole] = "author";
    m_roleNames[EMailRole] = "email";
    m_roleNames[MsgRole] = "message";
    m_roleNames[DateRole] = "date";
    m_roleNames[HashRole] = "hash";

    /*
    here we append the m_data (QList) Items using libgit2 methods
    */

}

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

QVariant CommitsBranch::data(const QModelIndex &index, int role) const
{    
    // this function returns the required data
}

QHash<int, QByteArray> CommitsBranch::roleNames() const
{
    return m_roleNames;
}

And git is just a class that inherits from QObject and it has the following method:

Q_INVOKABLE QObject* getCommitsBranch();
QObject *Git::getCommitsBranch()
{
    CommitsBranch* files = new CommitsBranch(repo.data());
    return files;
}

I get the same behavior without the scrollview.

EDIT: If I take a repository with a lot of commits (more lines to the listview), even increasing the cacheBuffer won't help, if I scroll a bit fast all the items will disappear.


Solution

  • The problem here is that, by default, if you return a QObject* it will transfer the ownership to QML.

    http://doc.qt.io/qt-5/qtqml-cppintegration-data.html#data-ownership

    The exception to this rule is when a QObject is returned from an explicit C++ method call: in this case, the QML engine assumes ownership of the object, unless the ownership of the object has explicitly been set to remain with C++ by invoking QQmlEngine::setObjectOwnership() with QQmlEngine::CppOwnership specified.

    You have to set the returned QObject* ownership manually, so it doesn't get destroyed by the QML engine :

    QObject *Git::getCommitsBranch()
    {
        CommitsBranch* files = new CommitsBranch(repo.data());
    
        QQmlEngine::setObjectOwnership(files, QQmlEngine::CppOwnership)
    
        return files;
    }
    

    Note that you will have a memory leak as your CommitsBranch object will never be deleted. But at least your QML items should not disappear anymore !

    EDIT: As suggested you can do something like this to avoid the memory leak :

    // CommitsBranch Constructor
    CommitsBranch::CommitsBranch(Repository *repo, QObject *parent) :
        QAbstractListModel(parent) { /*stuff*/ }
    
    QObject *Git::getCommitsBranch()
    {
        // Setting ownership is not necessary if you pass the parent to the QAbstractListModel
        CommitsBranch* commits = new CommitsBranch(repo.data(), this);
    
        return files;
    }