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

C++/QML: How to define and handle multiple contexts for dynamically created components?

本文关键字:定义 组件 处理 上下文 创建 QML 动态 c++      更新时间:2023-10-16

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

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

那么,在c++端,我有这样的东西:

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端,我有一个像这样的主文件:
// 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
    }
}

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

// 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的"the stuff",它被暴露为主上下文的上下文属性。

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

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

在文档中,我读到可以直接从c++创建组件并定义它应该使用的上下文…像这样的代码(从这里截取的片段):

QQmlEngine engine;
QStringListModel modelData;
QQmlContext *context = new QQmlContext(engine.rootContext());
context->setContextProperty("myModel", &modelData);
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0nListView { model: myModel }", QUrl());
QObject *window = component.create(context);

我想这对我打算做的事有用。每当我从c++中创建一个新视图时(由点击鼠标区域引起),我都会创建一个带有"someStuff"作为其属性的新上下文,以便每个视图都有自己的"stuff"…但是我需要从QML中访问新创建的视图,因为我在main中的addView()函数中创建它之后。我访问视图是为了做某些事情(不重要到底是什么),如果我从c++中创建组件的距离,我不知道如何从qml中访问它……是否有一种方法可以将组件从c++传递到QML以访问它?

我不知道如何解决这个问题,或者找到另一种方法来动态创建具有自定义内容的视图,同时所有可见…

我发现直接将用c++创建的组件传递给QML是可能的(而且很容易)。

现在,我把代码修改成这样:

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中函数在主。QML现在是这样的:

// 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
}

所以我设法没有改变代码太多。

我认为你可以通过设置一个对象作为上下文属性来传递你的组件实例(QObject),就像你在你的代码中做的那样。

class ViewInstance : public QObject
{
Q_OBJECT
    public:
    Q_INVOKABLE QObject* getCurrentViewInstance() {
        ...
        QObject *window = component.create(context);
        return window;
    }
};
int main(int argc, char *argv[]) {
    ...
    QQuickView view;
    ViewInstance data;
    view.rootContext()->setContextProperty("viewInstance", &data);
}

然后,在你的qml中,你可以通过调用viewInstance.getCurrentViewInstance()来获得组件实例。