Qt5.6 QML, why are dynamic models destroyed after garbage collection?

喜欢而已 提交于 2019-11-28 05:01:33

问题


I have a variable number of components, so i'm trying to give each one its own model. In this example, i just create one, but the idea is the same.

GC() is a bit random, so in the example, i force the gc() after a click to flush out the problem. What happens is that the model is destroyed and becomes null. after that the click method cannot use it.

main.qml:

import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.2

import com.example.qml 1.0

ApplicationWindow
{
    visible: true
    width: 640
    height: 480

    // builder of dynamic models
    ModelFactory { id: maker }

    Column
    {
        anchors.fill: parent
        Repeater
        {
            // create dynamic model
            model: maker.makeModel();
            delegate: Label
            {
                id: label
                text: model.name

                MouseArea
                {
                    anchors.fill: parent
                    onClicked:
                    {
                        // works once until gc()
                        console.log("clicked on " + model.name)

                        // wont work anymore. model is destroyed
                        gc();
                    }
                }
            }

        }
    }
}

c++/mymodel.h:

#include <QAbstractListModel>
#include <QQmlApplicationEngine>
#include <QObject>
#include <QString>
#include <QDebug>

class BoxModel : public QAbstractListModel
{
    Q_OBJECT

public:

    ~BoxModel()
    {
        // see that it does get destroyed
        qDebug() << "~BoxModel()";
    }

    int rowCount(const QModelIndex& parent = QModelIndex()) const override
    {
        return 5;
    }  

    QVariant data(const QModelIndex &index, int role) const override
    {
        int ix = index.row();
        if (ix < 1) return "Larry";
        if (ix < 2) return "Barry";
        if (ix < 3) return "Gary";
        if (ix < 4) return "Harry";
        return "Sally";
    }

    QHash<int, QByteArray> roleNames() const override
    {
        QHash<int, QByteArray> roles;
        roles[Qt::UserRole+1] = "name";
        return roles;
    }

};

class ModelFactory: public QObject
{
    Q_OBJECT

public:

    Q_INVOKABLE BoxModel* makeModel()
    {
        return new BoxModel();
    }    
};

main.cpp just registers the types:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <qqmlcontext.h>
#include <qqml.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
#include "mymodel.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    qmlRegisterType<BoxModel>("com.example.qml", 1, 0, "BoxModel");
    qmlRegisterType<ModelFactory>("com.example.qml", 1, 0, "ModelFactory");

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}

what you see:

Click on any of the names. it will work once and after that they will be undefined because model becomes null.

eg

qml: clicked on Sally
~BoxModel()
qml: clicked on undefined

My question is why is this, when i still have a reference to it?

In the example, onClicked could be changed to label.text rather than model.name to fix, but the real problem is that, in general, the model is accessed by the object at any time, for any data. For example, when the box needs to redraw. randomly the data is gone, depending on GC.

I've tried making c++ manage the life of the dynamic model. this could work if i know when exactly QML has finished with it.

thanks for info and ideas.

running on windows 8.1/qt5.6mingw

EDIT1: files as a gist, https://gist.github.com/anonymous/86118b67ec804e6149423c14792f312d


回答1:


As Kuba said, this does indeed seem like a bug. However, you can take another approach and take ownership of the models yourself via QQmlEngine::setObjectOwnership(). Specifically, changing

Q_INVOKABLE BoxModel* makeModel()
{
    return new BoxModel();
}

to

Q_INVOKABLE BoxModel* makeModel()
{
    BoxModel *model = new BoxModel(this);
    QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership);
    return model;
}

will fix this (remember to parent the returned model to BoxModel so it gets deleted appropriately). The reason for the behaviour is explained here:

Generally an application doesn't need to set an object's ownership explicitly. QML uses a heuristic to set the default ownership. By default, an object that is created by QML has JavaScriptOwnership. The exception to this are the root objects created by calling QQmlComponent::create() or QQmlComponent::beginCreate(), which have CppOwnership by default. The ownership of these root-level objects is considered to have been transferred to the C++ caller.

Objects not-created by QML have CppOwnership by default. The exception to this are objects returned from C++ method calls; their ownership will be set to JavaScriptOwnership. This applies only to explicit invocations of Q_INVOKABLE methods or slots, but not to property getter invocations.




回答2:


I just had the same problem with a ComboBox.

As a workaround, you may create your own property to keep a strong reference to it:

Repeater {
    property QtObject myModel: maker.makeModel();
    model: myModel
    // …
}



回答3:


I know this is an old question, but I've just faced similar issue, and found your question in process of writing mine. See QObject gets destroyed after being put into QML variable for full story, and I'll cite it here.

What I've figured out that if I set the parent of that QObject before I pass it into QML, then it doesn't get deleted. So, I've concluded that passing unparented QObject into QML scope makes that scope become a parent of QObject and call its destructor after scope ends.



来源:https://stackoverflow.com/questions/37325792/qt5-6-qml-why-are-dynamic-models-destroyed-after-garbage-collection

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!