将QDeclarativeContext层次结构与QDeclarativeView

时间:2017-01-22 19:46:15

标签: c++ qt qml

现在在我的C ++ / QML应用程序中,即使每个QML视图仅使用所有可用属性的子集,所有这些属性都通过QDeclarativeContext::setContextProperty的根QDeclarativeEngine中的QDeclarativeView公开。 import QtQuick 1.0 Rectangle { visible: true width: 800 height: 600 Text { text: message.text anchors.centerIn: parent } } 显示QML文件 - 这有点难看。

根据Qt documentation

  

应该只对子组件实例可用的附加数据添加到根上下文的子上下文中。

所以我想这样做。但是,我没有找到进一步的文档或信息如何实际使用子上下文。不幸的是,仅仅创建一个新的子上下文是不够的。

实施例

我有一个非常简单的QML文件

message

访问名为main的上下文属性,该属性设置在int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget widget{nullptr, Qt::FramelessWindowHint}; widget.resize(800, 600); MessageHolder message{"Hello World"}; // ceeate the new context and add the message to context properties QDeclarativeContext message_context{view.engine()}; message_context.setContextProperty("message", &message); // what needs to go here so that the new context is actually used? view.setSource(QUrl{"path/to/main.qml"}); view.show(); widget.show(); return app.exec(); } 中QDeclarativeView引擎的根上下文的子上下文中:

MessageHolder

上面使用的class MessageHolder : public QObject { Q_OBJECT Q_PROPERTY(QString text READ text CONSTANT) public: MessageHolder(QString text); QString text(); private: QString _text; }; 是一个简单的类,除了提供在构造这样一个对象时设置的常量QString之外什么都不做:

message

如上所述,遗憾的是,这并不起作用,QML会发出警告

  

/path/to/main.qml:9:ReferenceError:无法找到变量:message`

如果我改为将viewmodel属性添加到根上下文,一切正常。在创建上下文和设置视图源以使其工作之间我需要做什么?

更深入地解释我真正想做的事情

在我的应用程序中,我使用MVVM架构,其中每个QML视图都有相应的C ++视图模型(在MVVM意义上而不是Qt术语)。

在任何给定时刻,许多不同的视图模型中只有一个是活动的,但是在应用程序启动期间它们都会被实例化,然后通过上下文属性公开给QML。

我遇到的核心问题是每个视图模型需要一个唯一的名称,因此也将QML视图强烈耦合到单个视图模型。拥有名为viewmodel的单个通用上下文属性会更好。

但是,问题在于:当新的viewmodel变为活动状态时,我必须在更改QML视图之前或之后将Q_ASSERT context属性重新设置为新的viewmodel。但是更改上下文属性会导致重新评估当前活动的所有绑定。因此,当我第一次更改context属性然后更改视图时,即使新的viewmodel没有所需的属性,旧视图也会尝试更新,如果我首先更改视图然后更改上下文属性view将在加载时评估其所有绑定,但由于context属性尚未更新,因此尚未提供必要的属性。

在视觉上这不是问题,因为在第一种情况下,现在无论如何都要卸载现在被破坏的视图,而在第二种情况下,一旦设置了新的上下文属性,新视图将立即重新评估所有属性。然而,在任何一种情况下,QML都会因为缺少属性而发出警告,这首先是不好的,因为它使日志文件混乱,其次因为我通过viewmodel将警告视为调试版本中的致命错误,以便发现错误在我的持续集成系统的早期QML视图或视图模型中,无需人工检查。

但后来我偶然发现了Qt documentation中的以下事实:

  

虽然在上下文中实例化的QML对象并非由该上下文严格拥有,但它们的绑定是。如果上下文被破坏,未完成的QML对象的属性绑定将停止评估。

这正是我需要的,以阻止QML在垂死的视图中重新评估现在无效的绑定。因此,我想在子上下文中设置{{1}}上下文属性,只要视图发生变化,我就会销毁并重新创建。

1 个答案:

答案 0 :(得分:1)

好吧,您可以将其添加到不是根对象的特定对象的上下文中,而不是将属性设置为根(对象)上下文。

您可以使用此静态方法获取任何对象的上下文:

auto ctx = QQmlEngine::contextForObject(someQObjectPtr);

每个QML对象都有它的上下文,显然如果你像在代码中那样创建了一些任意上下文,它与任何东西都不对应,可以理解它不会按预期工作。

虽然你想做的事听起来有些落后。

我要做的是拥有另一个模型或至少包含所有模型的基本列表,然后设置property model activeModel并将其设置为我想要显示的模型,并使用activeModel视图,因此更改活动模型将自动更新视图。如果您想更明确,可以在更改模型之前手动销毁视图。

此外,正如this recent question所示,对象破坏本身并不会立即发生在QML中,它会被延迟,并且仍然会导致对被认为不再相关的对象的绑定进行重新评估。所以你的原始计划甚至可能都不起作用,除了是一个你不应该在一开始就没有的设计问题的不必要的补丁。

总而言之,我不建议将上下文属性用于比将单个对象暴露给QML更动态的任何内容。当你有很多东西时,它只是不可扩展和可维护,每个东西都需要自己的代码行来注册和它自己的名字。

更新:

  

仅应对组件子集可用的其他数据   应将实例添加到根目录的子上下文中   上下文。

你错误地解释了这个。它并没有告诉你去手动创建自己的上下文。我不熟悉任何means to manually specify which context you want from QML,上下文是隐式的 - 您所在的上下文 - 当前对象的上下文,或者如果找不到该属性,则将上下文一直搜索到根目录上下文。它并不意味着您应该无所事事地创建上下文树,然后删除它们,在qml引擎下拉地毯。

因此,正如我的初步答案所述,如果您不想在根上下文中注册属性,则应选择分支的上下文。然后该属性仅对该子树中的对象可见。 然而这不会真正解决您遇到的问题,至少不能以合理,有效和优雅的方式解决。

解决方案冒着重复自己的风险,非常简单明了 - 折叠视图,更改模型,重建视图。没有必要尝试做一些你不了解的不可能的事情。销毁上下文的唯一方法是销毁其对象,当视图被销毁时,将没有对象用无效数据更新其绑定。粗略地从C ++中删除QML对象无助于您以任何方式避免QML的设计限制,它只会导致应用程序崩溃。

这个围绕generic object model构建的简单示例清楚地表明了那些"残余"绑定只是QML内部设计的产物,根据我的个人经验,它存在漏洞,漏洞和设计限制。我自己已经有了(相当不公平)的斗争和辛劳,但如果经验教会了我什么,那就是尝试解决它,因为有时候QML的工作方式与逻辑和理性无关,所以你的期望是多么合理和符合逻辑(如果在这个特定情况下完全无关)是完全无关紧要的。

  List {
    id: models
    List {
      property Component delegate : Rectangle {
        width: 200
        height: 50
        color: object.apple
      }
      QtObject { property color apple: "green" }
      QtObject { property color apple: "red" }
      QtObject { property color apple: "yellow"}
    }
    List {
      property Component delegate : Rectangle {
        width: 200
        height: 50
        color: "red"
        Text {
          anchors.centerIn: parent
          text: object.pepper
        }
      }
      QtObject { property string pepper: "sweet" }
      QtObject { property string pepper: "spycy" }
      QtObject { property string pepper: "hot" }
    }
  }

  Row {
    ListView {
      width: 200
      height: 200
      model: models
      delegate: Rectangle {
        width: 200
        height: 50
        color: "grey"
        Text {
          anchors.centerIn: parent
          text: index
        }
        MouseArea {
          anchors.fill: parent
          onClicked: {
            currentModel.model = null // remove line and suffer
            currentModel.model = object
          }
        }
      }
    }
    ListView {
      id: currentModel
      width: 200
      height: 200
      model: null
      delegate: model ? model.delegate : null
    }
  }

实现是一个列表列表,或者是模型模型。模型列表中的每个模型都有自己的委托,以及具有自己的属性集的对象。有两个列表视图,第一个列出所有可用模型,第二个列出活动模型的内容(如果有)。如果没有currentModel.model = null行,每次切换当前模型时,您都会收到有关未定义属性的控制台错误,因为这就是QML的工作原理。实际上非常幸运的是,在这种情况下,解决方案就像在切换到另一个之前null模型一样简单,使用该行一切都很好,视图元素在模型切换之前被销毁,没有它项目会在一个事件循环周期中停留,足以产生错误,而那些不是来自当前列表视图项目,而是来自那些正在消失的项目,所以即使你delete在C ++中的数据中,由列表视图管理的那些对象会徘徊不足以制造麻烦,而在delete的情况下,它不会是控制台错误,而是硬应用程序崩溃。 / p>