C++ 安全删除StackView转换中使用的QML组件 概述

C++ 安全删除StackView转换中使用的QML组件 概述,c++,qt,qml,C++,Qt,Qml,我的问题涉及由创建的QObject的生命周期。create()返回的对象是qqqmlcomponent的实例化,我正在将其添加到QMLStackView。我在C++中创建对象并传递给QML,在 StaveVIEW/COD>中显示。问题是,当我从堆栈中弹出一个项目时,会出现错误。我编写了一个演示应用程序来说明发生了什么 免责声明:是的,我知道从C++中输入QML不是“最佳实践”。是的,我知道你应该在QML做UI。然而,在生产世界中,有大量的C++代码需要与UI共享,因此需要在C++和CML之间进行

我的问题涉及由创建的
QObject
的生命周期。
create()
返回的对象是
qqqmlcomponent
的实例化,我正在将其添加到QML
StackView
。我在C++中创建对象并传递给QML,在 StaveVIEW/COD>中显示。问题是,当我从堆栈中弹出一个项目时,会出现错误。我编写了一个演示应用程序来说明发生了什么

<>免责声明:是的,我知道从C++中输入QML不是“最佳实践”。是的,我知道你应该在QML做UI。然而,在生产世界中,有大量的C++代码需要与UI共享,因此需要在C++和CML之间进行一些互操作。我使用的主要机制是通过在C++侧设置上下文来编写代码> QuyField绑定。 此屏幕是演示启动时的样子:

StackView位于中间,背景为灰色,其中有一项(文本为“默认视图”);此项由QML实例化和管理。现在,如果按下<强>推/强>按钮,则C++后端从<代码> VIEA.QML 创建对象并将其放置在堆栈上。这里是一个屏幕截图,显示:

此时,我按Pop
StackView
中删除“视图A”(上图中为红色)。C++调用QML从堆栈中弹出该项,然后删除它创建的对象。问题是QML需要这个对象来进行转换动画(我使用默认动画为<代码> StaveVIEW/COD>),并且在从C++中删除它时,它会抱怨。所以我想我理解为什么会发生这种情况,但我不确定如何找出QML何时处理完对象,以便删除它<强>如何确保QML是用C++创建的对象完成的,这样我就可以安全删除它了吗?< /强> < /P> 总而言之,以下是重现我描述的问题的步骤:

  • 启动程序
  • 单击推送
  • 单击Pop
  • 以下输出显示了在上述步骤3中弹出项目时发生的
    TypeError
    s:

    输出 在下面的输出中,我按一次“推”,然后按“弹出”。请注意调用
    ~ViewA()
    时出现的两个
    TypeError
    s

    root object name =  "appWindow"
    [c++] pushView() called
    qml: [qml] pushView called with QQuickRectangle(0xdf4c00, "my view")
    [c++] popView() called
    qml: [qml] popView called
    [c++] deleting view
    ~ViewA() called
    file:///opt/Qt5.8.0/5.8/gcc_64/qml/QtQuick/Controls/Private/StackViewSlideDelegate.qml:97: TypeError: Cannot read property 'width' of null
    file:///opt/Qt5.8.0/5.8/gcc_64/qml/QtQuick/Controls/StackView.qml:899: TypeError: Type error    
    
    上下文必须从C++中设置

    显然,发生的是 StaveVIEW/COD>使用的对象(项目)正在被C++删除,但QML仍然需要这个项目用于过渡动画。我想我可以在QML中创建对象,让QML引擎管理生命周期,但是我需要设置对象的<代码> qqMListabor <代码>,以将QML视图绑定到C++上的“代码> QyFielsS”。 请参阅我的相关问题

    代码示例 我已经生成了一个最小完整的示例来说明这个问题。下面列出了所有文件。特别是,请查看
    ~ViewA()
    中的代码注释




    //viewa.h
    #包括
    类QQmlContext;
    QQmlEngine类;
    类QObject;
    类ViewA:公共QObject
    {
    Q_对象
    公众:
    显式视图A(QQmlEngine*引擎,QQmlContext*上下文,QObject*父对象=0);
    virtual~ViewA();
    //假设此视图具有“上下文”使用的属性绑定
    //Q_属性(类型名称读取名称写入集合名称通知名称更改)
    QQmlContext*context=nullptr;
    QObject*object=nullptr;
    };
    

    //viewa.cpp
    #包括“viewa.h”
    #包括
    #包括
    #包括
    #包括
    ViewA::ViewA(QQmlEngine*引擎、QQmlContext*上下文、QObject*父对象):
    QObject(父对象),
    上下文(context)
    {
    //使属性绑定对创建的组件可见
    this->context->setContextProperty(“ViewAContext”,this);
    QQmlComponent组件(引擎,QUrl(QLatin1String(“qrc:/ViewA.qml”));
    object=component.create(上下文);
    }
    ViewA::~ViewA()
    {
    
    我正在发布一个自己的问题的答案。如果你发布一个答案,我会考虑接受你的答案而不是这个答案。但是,这是一个可能的工作。

    问题是,在C++中创建的QML对象需要足够长的时间才能使QML引擎完成所有的转换。我使用的技巧是标记QML对象实例以删除,等待QML完成动画几秒钟,然后删除对象。这里的一部分是,我必须猜测我应该等待多少秒,直到我认为QML完全完成了这个对象

    首先,我列出了计划要销毁的对象。我还创建了一个插槽,在延迟后将调用该插槽以实际删除该对象:

    class ViewManager : public QObject {
    public:
        ...
        QList<ViewA*> garbageBin;
    public slots:
        void deleteAfterDelay();
    }
    
    几秒钟后,调用
    deleteAfterDelay()
    插槽并“垃圾收集”该项:

    void ViewManager::deleteAfterDelay()
    {
        if (garbageBin.count() > 0) {
            auto view = garbageBin.takeFirst();
            qDebug() << "[c++] delayed delete activated for " << view->objectName();
            delete view;
        }
    }
    
    void ViewManager::deleteAfterDelay()
    {
    if(garbageBin.count()>0){
    auto view=garbageBin.takeFirst();
    
    qDebug()我相信我已经找到了一种方法来丢弃@Matthew Kraus推荐的垃圾列表。我让QML在弹出StackView时销毁视图

    警告:代码片段不完整,仅用于说明OP文章的扩展

    功能推送视图(项目,id){
    //附加选项以自动销毁pop(由C++调用)
    push(项,{},{“destroyOnPop”:true})
    }
    函数popView(id){
    //立即弹出(删除过渡效果)并验证视图
    //已删除(空)。否则,请立即删除。
    var old=rootStackView.pop({“item”:null,“immediate”:true})
    如果(旧的!==null){
    旧的.Debug()/ /要求C++分配QML所有权
    }
    //C++中的MyAccVistRistor视图。
    //完成了他的工作
    viewManager.onViewClosed(id)
    }
    
    你会很快发现,如果删除,口译员会对你大喊大叫
    // viewa.h
        #include <QObject>
    
    class QQmlContext;
    class QQmlEngine;
    class QObject;
    
    class ViewA : public QObject
    {
        Q_OBJECT
    public:
        explicit ViewA(QQmlEngine* engine, QQmlContext* context, QObject *parent = 0);
        virtual ~ViewA();
    
        // imagine that this view has property bindings used by 'context'
        // Q_PROPERTY(type name READ name WRITE setName NOTIFY nameChanged)
    
        QQmlContext* context = nullptr;
        QObject* object = nullptr;
    };
    
    // viewa.cpp
    #include "viewa.h"
    #include <QQmlEngine>
    #include <QQmlContext>
    #include <QQmlComponent>
    #include <QDebug>
    
    ViewA::ViewA(QQmlEngine* engine, QQmlContext *context, QObject *parent) :
        QObject(parent),
        context(context)
    {
        // make property bindings visible to created component
        this->context->setContextProperty("ViewAContext", this);
    
        QQmlComponent component(engine, QUrl(QLatin1String("qrc:/ViewA.qml")));
        object = component.create(context);
    }
    
    ViewA::~ViewA()
    {
        qDebug() << "~ViewA() called";
        // Deleting 'object' in this destructor causes errors
        // because it is an instance of a QML component that is
        // being used in a transition. Deleting it here causes a
        // TypeError in both StackViewSlideDelegate.qml and
        // StackView.qml. If 'object' is not deleted here, then
        // no TypeError happens, but then 'object' is leaked.
        // How should 'object' be safely deleted?
    
        delete object;  // <--- this line causes errors
    
        delete context;
    }   
    
    // viewmanager.h
    #include <QObject>
    
    class ViewA;
    class QQuickItem;
    class QQmlEngine;
    
    class ViewManager : public QObject
    {
        Q_OBJECT
    public:
        explicit ViewManager(QQmlEngine* engine, QObject* topLevelView, QObject *parent = 0);
    
        QList<ViewA*> listOfViews;
        QQmlEngine* engine;
        QObject* topLevelView;
    
    public slots:
        void pushView();
        void popView();
    };
    
    // viewmanager.cpp
    #include "viewmanager.h"
    #include "viewa.h"
    #include <QQmlEngine>
    #include <QQmlContext>
    #include <QDebug>
    #include <QMetaMethod>
    
    ViewManager::ViewManager(QQmlEngine* engine, QObject* topLevelView, QObject *parent) :
        QObject(parent),
        engine(engine),
        topLevelView(topLevelView)
    {
        QObject::connect(topLevelView, SIGNAL(signalPushView()), this, SLOT(pushView()));
        QObject::connect(topLevelView, SIGNAL(signalPopView()), this, SLOT(popView()));
    }
    
    void ViewManager::pushView()
    {
        qDebug() << "[c++] pushView() called";
    
        // create child context
        QQmlContext* context = new QQmlContext(engine->rootContext());
    
        auto view = new ViewA(engine, context);
        listOfViews.append(view);
    
        QMetaObject::invokeMethod(topLevelView, "pushView",
            Q_ARG(QVariant, QVariant::fromValue(view->object)));
    }
    
    void ViewManager::popView()
    {
        qDebug() << "[c++] popView() called";
    
        if (listOfViews.count() <= 0) {
            qDebug() << "[c++] popView(): no views are on the stack.";
            return;
        }
    
        QMetaObject::invokeMethod(topLevelView, "popView");
    
        qDebug() << "[c++] deleting view";
        auto view = listOfViews.takeLast();
        delete view;
    }
    
    // main.cpp
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include <QQuickView>
    #include <QQuickItem>
    #include "viewmanager.h"
    #include <QDebug>
    
    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
    
        QQuickView view;
        view.setSource(QUrl(QLatin1String("qrc:/main.qml")));
    
        QObject* item = view.rootObject();
        qDebug() << "root object name = " << item->objectName();
        ViewManager viewManager(view.engine(), item);
    
        view.show();
        return app.exec();
    }
    
    class ViewManager : public QObject {
    public:
        ...
        QList<ViewA*> garbageBin;
    public slots:
        void deleteAfterDelay();
    }
    
    void ViewManager::popView()
    {
        if (listOfViews.count() <= 0) {
            qDebug() << "[c++] popView(): no views are on the stack.";
            return;
        }
    
        QMetaObject::invokeMethod(topLevelView, "popView");
    
        // schedule the object for deletion in a few seconds
        garbageBin.append(listOfViews.takeLast());
        QTimer::singleShot(2000, this, SLOT(deleteAfterDelay()));
    }
    
    void ViewManager::deleteAfterDelay()
    {
        if (garbageBin.count() > 0) {
            auto view = garbageBin.takeFirst();
            qDebug() << "[c++] delayed delete activated for " << view->objectName();
            delete view;
        }
    }