2

I would like to set a context property of a specific QML Component instead of in the root context. I do not want the property accessible outside of the component. Is there a way from C++ to access the context of the Component to only allow the named property to be accessible from within the component's context, and not from the global namespace? I would like to keep the QML declarative and to not create the component in C++ to access it's context.

//main.qml
Item {
    Button {
        // this should NOT work
        text: ctxProp.text
     }
     OtherQml {
     }
}

//OtherQml.qml
Item {
    Button {
        // this should work
        text: ctxProp.text
    }
}

//main.cpp
QGuiApplication app(art, argv);
QQmlQpplicationEngine engine;

// Some QObject Type
CtxProp ctxProp;
// I'd like to set the context such that only OtherQml.qml can access
// this context propery. Setting in root context property makes it global
engine.rootContext()->setContextProperty("ctxProp", &ctxProp);
dtech
  • 47,916
  • 17
  • 112
  • 190

1 Answers1

7

You should implement a singleton that will only be visible where it is imported. This is the best and most efficient solution for exposing core logic to QML, as there is no tree lookup involved, and it is only visible where you chose to import it.

qmlRegisterSingletonType<CtxProp>("Sys", 1, 0, "Core", getCoreFoo);
// and in QML
import Sys 1.0
...
doStuffWith(Core.text)

getCoreFoo is the name of a function that returns a CtxProp * value (any QObject * would do actually due to the usage of meta data). You may create it in the function, or just return a pointer to an existing instance. Some people claimed there might be issues if the function doesn't create it since it is presumably managed by the QML engine, however I've been using a pre-existing one that I am sharing across multiple QML engines and haven't had a problem with it, of course setting ownership to C++ explicitly, since QML cannot really be trusted with managing the lifetime of objects in more dynamic usage contexts.

// main.cpp

static Core * c;
static QObject * getCore(QQmlEngine *, QJSEngine *) { return c; }

int main(int argc, char *argv[]) {
  QGuiApplication app(argc, argv);
  QQmlApplicationEngine engine; 
  c = new Core; // create
  engine.setObjectOwnership(c, QQmlEngine::CppOwnership); // don't manage
  qmlRegisterSingletonType<Core>("Sys", 1, 0, "Core", getCore); // reg singleton
  engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); // load main qml
  return app.exec();
}
dtech
  • 47,916
  • 17
  • 112
  • 190
  • Thanks dtech. I updated my question to elaborate on the origins of `ctxProp`. Is there a way to "hide" `ctxProp` in this situation? – Ryan Schaefer Mar 20 '18 at 18:12
  • 1
    @RyanSchaefer Context properties are all over the trivial qml examples, but I personally don't consider them to be a good practice. OK for examples I guess, but people tend to take it literally and go overboard using it, when in fact for every context property use there is actually a better way to do it. In your particular case, you set the property for the root object context, therefore it will be visible to the entire object tree unless shadowed. If you don't want that to be the case, don't set it for the root context - plain and simple. – dtech Mar 20 '18 at 18:32
  • If it is a "core logic" C++ object you need exposed to QML, then register it as a singleton and import where needed. If it is just some value, you don't need to touch C++ for that. – dtech Mar 20 '18 at 18:34
  • thanks for the hints. I'm just getting started with QML and have read a lot of examples. If you don't think context properties are good practice, how do you usually go about working with the business logic of a QML application? I guess my ultimate goal here is to have C++ object encapsulate the business logic, and to limit the scope of the business logic accessible by different QML components / files. **Edit**: just saw your comment above. Thanks for the advice! – Ryan Schaefer Mar 20 '18 at 18:37
  • Singletons are the best solution. A context property might take a lot of time to resolve, depending on how deep you are in the object tree, it has to go down every component scope until it hits the root context. With a singleton you say "this is what I want" so it is far more efficient. And it is only visible where you chose to import it. – dtech Mar 20 '18 at 18:38
  • I have started using a Singleton but I don't think it will fit my use case. @dtech The core logic class is responsible for interacting with another thread that communicates over DBus to other processes. Now that the instance is managed by the QML Application Engine, I am not sure how I can connect a signal to the core logic instance's slot to notify when a message is received in the dbus thread. – Ryan Schaefer Mar 20 '18 at 21:09
  • Well, qml essentially lives in the main thread, multithreading won't work directly regardless of what method you use to expose C++ objects to QML. There is a work around of that thou - you have to use signal mediator object as described in the example here https://stackoverflow.com/questions/46738764/is-there-a-way-to-run-c-from-a-qml-workerscript/46739399#46739399 – dtech Mar 20 '18 at 21:17
  • Ignoring the threading issue, is there no way to access the singleton instance controlled by the engine to connect signals and slots, or call instance member functions from C++? – Ryan Schaefer Mar 20 '18 at 21:35
  • It doesn't have to be managed by the engine. Threading is not an issue as long as you do the mediator as evident from the linked answer. There is no problem here. You just have to do the inter-thread connections from C++, from the threaded object to the mediator that will live in the main thread and be the one that is exposed to QML. – dtech Mar 20 '18 at 21:42
  • I'm ignoring threading entirely. Threading is **not** what I am asking about. If I have a registerSingletonType(...), an instance is instantiated on first use in QML by the Application Engine. In order to `connect` slots and signals from C++, I would need a reference to that instance correct? If I cannot get an instance, does that mean that I should use "setContextProperty" instead with a reference to the object owned by C++? I looked at your other answer about threading and it will be helpful once I get a more complete framework figured out. – Ryan Schaefer Mar 20 '18 at 21:55
  • It doesn't have to be instantiated by the qml engine. You can create the object in main.cpp just like in your code, only before loading the qml. – dtech Mar 20 '18 at 21:59