C++/QML:如何为动态创建的组件定义和处理多个上下文?

2022-01-19 00:00:00 qt qml c++

基本上我的情况是这样的:

Basically my situation is like this:

我有一个扩展 QQuickView 并通过设置上下文属性将某些对象从 C++ 公开到 QML 的类.显示的视图是从 QML 创建的,并且都是同一个定制组件的不同实例;当某些事件发生时会创建新视图,当这种情况发生时,现有视图应该显示最初在 C++ 端分配给它们的对象,而新视图应该显示分配给它们的东西.

I've got a class that extends QQuickView and that exposes certain objects from C++ to QML by setting context properties. The views that are shown are created from QML and are all different istances of the same custom made component; new views are created when certain events occur, and when that happens the existing views should show the objects that were initially assigned to them in the C++ side, and the new ones should show the things assigned to them.

所以,在 C++ 方面,我有这样的东西:

So, in the C++ side I've got something like this:

WindowManager::WindowManager(QQuickView *parent) :
QQuickView(parent)
{
      // Setting the source file to use
      this->setSource(QUrl("qrc:/qml/main.qml"));

      // Exposing the istance of this class to QML for later use
      this->rootContext()->setContextProperty("qquickView", this);

      // Calling the method that will create dynamically a new view that will be child of main.qml; the parameter is not important, just a random number to start with
      this->prepareNewView(3)

      this->showFullScreen();
}

WindowManager::prepareNewView(int stuffId)
{
      MyDatabase db;

      // Getting something to show in QML from somewhere based on the parameter received
      SomeStuff stuff = db.getStuff(stuffId)

      // Exposing the object I need to show in QML
      this->rootContext()->setContextProperty("someStuff", stuff);



      QObject *object = this->rootObject();

      // Here I'm invoking a function from main.qml that will add a new view dynamically
      QMetaObject::invokeMethod(object, "addView");
}

现在,在 QML 方面,我有一个像这样的主文件:

Now, in the QML side I've got a main file like this:

// main.qml
Rectangle {
    id: mainWindow
    width: 1000
    height: 1000

    // This function adds a component to mainWindow
    function addView()
    {
        // Creating the component from my custom made component
        var component = Qt.createComponent("MyComponent.qml");

        // Creating an istance of that component as a child of mainWindow
        var newView = component.createObject(mainWindow);


        // ... Now I would be doing something with this new view, like connecting signals to slots and such
    }
}

然后我得到了我的自定义组件,这是将动态创建的视图:

Then I've got my custom component, which is the view that will be created dynamically:

// MyComponent.qml
Rectangle {
    id: customComponent

    // Here I would be using the object I exposed from the C++ side
    x: someStuff.x
    y: someStuff.y
    width: someStuff.width
    height: someStuff.height

    // Here I'm creating a MouseArea so that clicking this component will cause the creation of another view, that will have to show diffrent things since the parameter I'm passing should be different from the starting parameter passed in the constructor of WindowManager
    MouseArea {
        anchors.fill: parent
        onClicked: qquickView.prepareNewView(Math.random())
    }
}

现在,一切都保持原样,首先它会显示 id 为 3 的东西",它作为主上下文的上下文属性公开.

Now, with everything as it is, at first it will show "the stuff" with id 3, that was exposed as a context property of the main context.

但是,如果我点击 MouseArea,假设将传递一个 3 以外的 id,则会暴露一个新的同名上下文属性,从而导致旧属性的覆盖.这意味着第一个视图现在将显示刚刚暴露的东西",而不是基于 stuffId 等于 3 的东西",而我需要的是第一个视图来继续显示它应该显示的内容(东西" id = 3),以及稍后将出现的任何其他视图与其 id 对应的事物.

But then, if I click on the MouseArea, assuming that an id other than 3 will be passed, a new context property with the same name will be exposed, causing the override of the old property. This means that the first view will now show "the stuff" just exposed, not "the stuff" based from the stuffId equals to 3, while what I need is the first view to keep showing what it was supposed to show ("the stuff" with id = 3), and any other view that will come later the things corresponding to their ids.

发生这种情况是因为我在每个组件通用的上下文中定义了一个属性,而我应该定义一个仅由动态创建的组件的新实例可见的属性.但是我该怎么做呢?

This happens because I'm defining a property in the context that is common to every component, while I should be defining a property that is visible ONLY by the new istance of the component that is being created dynamically. But how do I do that?

在文档中,我读到可以直接从 C++ 创建一个组件并定义它应该使用的上下文......类似这样的东西(摘自 这里):

In the documentation I read that it's possibile to create a component directly from C++ and defining the context that it should use... something like this (snippet taken from here):

QQmlEngine engine;
QStringListModel modelData;
QQmlContext *context = new QQmlContext(engine.rootContext());
context->setContextProperty("myModel", &modelData);

QQmlComponent component(&engine);
component.setData("import QtQuick 2.0
ListView { model: myModel }", QUrl());
QObject *window = component.create(context);

我认为这对我打算做的事情有用.每当我从 C++ 创建一个新视图(由单击 MouseArea 引起)时,我都会创建一个以someStuff"作为其属性的新上下文,以便每个视图都有自己的东西"......但我需要访问从 QML 新创建的视图,因为在我在 main.qml 中的 addView() 函数中创建它之后,我访问视图以执行某些细化(不重要到底是什么),并且如果我从 C++ 创建组件的 istance我不知道如何从 QML 访问它...有没有办法将组件从 C++ 传递到 QML 以便访问它?

I think that this would work for what I intend to do. Whenever I create a new view from C++ (caused by the click on the MouseArea) I create a new context with "someStuff" as its property, so that each view has its own "stuff"... but I need to have access to the newly created view from QML, because after I create it in the addView() function inside main.qml I access the view in order to do certain thins (not important what exactly), and if I create the istance of the component from C++ I don't know how to access it from QML... is there a way maybe to pass the component from C++ to QML in order to access it?

我不知道如何解决这个问题或找到另一种方法来动态创建具有自定义内容的视图,所有这些视图都同时可见......任何建议都值得赞赏.

I'm out of ideas on how to resolve this or to find another way to have dynamically created views with custom contents all visible at the same time... any suggestions are appreciated.

推荐答案

我实际上发现可以(而且很容易)将用 C++ 创建的组件直接传递给 QML.

I actually found out that it is possible (and easy) to directly pass a component created in C++ to QML.

所以现在,我修改的代码差不多是这样的:

So right now, I modified the code pretty much like this:

WindowManager::prepareNewView(int stuffId)
{
    MyDatabase db;

    // Getting something to show in QML from somewhere based on the parameter received
    SomeStuff stuff = db.getStuff(stuffId)


    // Creating the new context, based on the global one
    QQmlContext *context = new QQmlContext(this->rootContext());


    // Exposing the object I need to show in QML to the new context
    context ->setContextProperty("someStuff", stuff);

    // Creating the component
    QQmlComponent component(this->engine(), QUrl("qrc:/qml/MyComponent.qml"));

    // Creating the istance of the new component using the new context
    QQuickItem *newView = qobject_cast<QQuickItem*>(component.create(context));


    // Getting the root component (the Rectangle with it mainWindow)
    QObject *object = this->rootObject();

    // Manually setting the new component as a child of mainWIndow
    newView->setParentItem(qobject_cast<QQuickItem*>(object));

    // Invoking the QML that will connect the events of the new window, while passing the component created above as QVariant
    QMetaObject::invokeMethod(object, "addView", Q_ARG(QVariant, QVariant::fromValue(newView)));
 }

在 QML 中 main.qml 中的函数现在是这样的:

In QML the function in the main.qml is now like this:

// Function called from C++; the param "newView" is the last component added
function addView(newView)
{
    // ... Here I would use the new view to connect signals to slots and such as if I created "newView" directly in QML
}

所以我还是设法没有对代码做太多改动.

So I managed not to change the code too much after all.

相关文章