Right now in my C++/QML application, even though every QML view only uses a subset of all available properties, all of them are exposed via QDeclarativeContext::setContextProperty
in the root context of the QDeclarativeEngine
of the QDeclarativeView
displaying the QML files - which is kind of ugly.
According to the Qt documentation,
Additional data that should only be available to a subset of component instances should be added to sub-contexts parented to the root context.
So I'd like to do that. However, I have not found further documentation or information how to actually use sub-contexts. Unfortunately, simply creating a new sub-context is not enough.
Example:
I have a very simple QML file
import QtQuick 1.0
Rectangle {
visible: true
width: 800
height: 600
Text {
text: message.text
anchors.centerIn: parent
}
}
that accesses a context property called message
, which is set inside a sub-context of the root context of the engine of a QDeclarativeView in 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();
}
The MessageHolder
used above is a simple class that does nothing but provide a constant QString that is set on construction of such an object:
class MessageHolder : public QObject {
Q_OBJECT
Q_PROPERTY(QString text READ text CONSTANT)
public:
MessageHolder(QString text);
QString text();
private:
QString _text;
};
As already stated above, this unfortunately doesn't work and QML emits the warning
/path/to/main.qml:9: ReferenceError: Can't find variable: message`
If I instead add the message
property to the root context everything works fine. What do I need to do between creating the context and setting the source of the view to make this work?
More in-depth explanation of what I'm really trying to do
In my application I use a MVVM architecture where each QML view has a corresponding C++ viewmodel (in the sense of MVVM and not the Qt terminology).
At any given moment there is only one of the many different viewmodels active, however all of them are instantiated during startup of the application and then exposed to QML via context properties.
The core issue I have with this is the need for a unique name for each of the viewmodels, thus also strongly coupling the QML views to a single viewmodel. It would be much nicer to have a single generic context property with the name viewmodel
instead.
However, here is the problem: When a new viewmodel becomes active I have to re-set the viewmodel
context property to the new viewmodel either before or after changing the QML view. But changing the context property leads to reevaluation of all bindings currently active. Therefore, when I first change the context property and then the view, the old view will try to update even though the new viewmodel doesn't have the required properties, and if I first change the view and then the context property the new view will evaluate all of its bindings when it is loaded, but the necessary properties are not yet available since the context property hasn't been updated yet.
Visually this is not a problem, since in the first case the now broken view is about to be unloaded anyway and in the second case the new view will immediately reevaluate all properties once the new context property is set. Nevertheless, in either case QML will emit a warning because of missing properties, which is not nice firstly because it clutters the log files and secondly because I treat warnings as fatal errors in debug builds via a Q_ASSERT
in order to spot bugs in QML views or viewmodels early in my continuous integration system without manual inspection.
But then I stumbled across the following fact in the Qt documentation:
While QML objects instantiated in a context are not strictly owned by that context, their bindings are. If a context is destroyed, the property bindings of outstanding QML objects will stop evaluating.
This is exactly what I need in order to stop QML from reevaluating now invalid bindings in the dying view. Therefore I'd like to set the viewmodel
context property in a sub-context which I'll destroy and recreate whenever the view changes.