3

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.

Community
  • 1
  • 1
Abdelilah El Aissaoui
  • 4,204
  • 2
  • 27
  • 47
  • The issue probably stems from the automatic creation and destruction of delegates by the view as you scroll. A dirty quick solution would be to increase the `cacheBuffer` of the view - that is the amount of pixels to preload. QML is known to sometimes be [losing track of its sheep](http://stackoverflow.com/questions/33792876/qml-garbage-collection-deletes-objects-still-in-use) so to speak, check whether scrolling doesn't accidentally delete the actual model items. – dtech Feb 07 '17 at 23:00
  • I've tried increasing the `cacheBuffer` to 100 and the problem still persists. I've changed it again to 1000 and now items do not disappear but sometimes I still get undefined in item's properties (also some weird visual bugs like text moved o.o) – Abdelilah El Aissaoui Feb 08 '17 at 08:34
  • Try to wrap an `Item` around the `Rectangle` in your delegate. Like `delegate: Item { Rectangle { ... } }` – DuKes0mE Feb 08 '17 at 11:26
  • @DuKes0mE the result is exactly the same, nothing changes :s – Abdelilah El Aissaoui Feb 08 '17 at 13:19
  • Hmm, it should avoid your rectangles being deleted. – DuKes0mE Feb 08 '17 at 13:26
  • It doesn't, nothing changed. @ddriver edited post mentioning something about cacheBuffer. – Abdelilah El Aissaoui Feb 08 '17 at 13:41
  • Remember, the cacheBuffer is in pixels not number of elements, so 100 is almost close to nothing. Several thousand will solve your problem, by effectively forcing elements to never be auto-deleted when scrolling away from them. Or just use a `Repeater` inside a `Flickable` - it will always create the full number of elements you have in the model and then you can flick manually. – dtech Feb 08 '17 at 14:17
  • Yes, I know but this listview can grow to thousands of registers so I can't use something like a Repeater + Flickable if I understand how they work. – Abdelilah El Aissaoui Feb 08 '17 at 15:09
  • Did you try static model (e.g. ListModel) for your ListView to exclude the possibility that the problem in the model? – Artem Zaytsev Feb 08 '17 at 15:15
  • @ArtemZaytsev with static model the problem seems to disappear but if I get the data from c++ the problem persists. That's really weird! – Abdelilah El Aissaoui Feb 08 '17 at 19:25

1 Answers1

4

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;
}
Abdelilah El Aissaoui
  • 4,204
  • 2
  • 27
  • 47
Blabdouze
  • 1,041
  • 6
  • 12
  • You're not wrong, but the CommitsBranch seems to be the list model, that cannot be deleted by QML while scrolling the list I guess!? – Xander Feb 09 '17 at 11:38
  • The model isn't being destroyed by QML when you scroll the list, but when QML assumes it don't need the model anymore. It can happen at any moment. If the list doesn't move, QML doesn't need to access the model, so you can't tell if the model is here or not. A scroll however will force QML to read the model and you will notice that something is off. – Blabdouze Feb 09 '17 at 13:23
  • I'll test it ASAP. To avoid the memory leak should I use a QSharedPointer or it wouldn't be a good idea? – Abdelilah El Aissaoui Feb 09 '17 at 15:53
  • It works! Thank you so much! Also would work this: `CommitsBranch* commits = new CommitsBranch(repo.data(), (QObject*)this);` and changing the constructor to `CommitsBranch(Repository *repo, QObject *parent);` and `CommitsBranch::CommitsBranch(Repository *repo, QObject *parent): QAbstractListModel(parent){/*stuff*/}`. If you don't mind, add it to your answer as an alternative. – Abdelilah El Aissaoui Feb 09 '17 at 19:28
  • Shared pointers don't work with QML, it uses its own shared object classes, which are not part of the public api. Which is very annoying because QML memory management is **broken** in several use cases and you have to do manual memory management like it was done 20 years ago... There is a bug report about it, but no work has been done on it in over 2 years, even though it was deemed critical: https://bugreports.qt.io/browse/QTBUG-50319 – dtech Mar 07 '18 at 23:59