Read property from QML singleton with C++ - c++

Is it possible to access/read the properties of a QML singleton inside your C++ code?
For example if my QML singleton looks like this:
pragma Singleton
import QtQuick 2.5
QtObject {
property int myProperty: 5
}
How can I access myProperty from C++ code. I need this as I do not want to have my "magic" numbers both in QML and C++ and it is only very rarely required in C++.
For normal QQuickItem's it was always easy. Just get access to the QuickItem (by dynamic creating it or with findChild()) and than call quickItem->property("myProperty").toInt()
But with the singleton I can't see how to get access to it.

Although not directly, one way to access a QML singleton is via a function in a non-singleton QML object, that you can access in the usual way:
Constants.qml
pragma Singleton
import QtQuick 2.5
QtObject {
objectName: "Constants"
property double phi: 1.6180339887498948482
}
main.qml (e.g.)
import QtQuick 2.5
import "."
function getPhi()
{
return Constants.phi;
}
C++
//...
// Create the engine and load QML
//...
QObject* rootObject = engine->rootObjects().constFirst();
QVariant phi;
QMetaObject::invokeMethod(rootObject, "getPhi", Q_RETURN_ARG(QVariant, phi));
qDebug() << phi.toFloat();
Don't forget you'll need a qmldir file to access the singleton in QML:
qmldir
singleton Constants Constants.qml

Related

Add a QTreeView in QML

I would like to register a QTreeView c++ object to QML.
I tried to register it like this:
main.cpp:
qmlRegisterType<QTreeView>("com.MyApp.QTreeView", 1, 0, "QTreeView");
relevant code in main.qml
import com.MyApp.QTreeView 1.0
QWindow {
QTreeView{
headerHidden: true
}
}
Result: it compiles. headerHidden property is found so it is registered correctly. However I have an error at runtime:
ASSERT: "!d->isWidget" in file kernel\qobject.cpp, line 2090
QWidgets are not directly compatible with QML such that they can be embedded in a QML view. They are two different UI technologies and cannot be used together in that fashion.
You can however embed a QML view inside of a QWidget hierarchy:
https://www.ics.com/blog/combining-qt-widgets-and-qml-qwidgetcreatewindowcontainer
Or just use the QML TreeView component instead:
https://doc.qt.io/qttreeview/qml-treeview.html

QML: C++ classes with "bring your own component"

I'm trying to develop a Qt C++ application, with a QML frontend, but I hit a roadblock.
This is what I have so far:
A Factory class that outputs a choice of objects. These objects, that I'm going to call "controllers", control different pieces of hardware.
The Factory would be exposed to the QML layer with setContextProperty.
The controller would be chosen basically with a combo box controlling the factory.
Now, for the tricky bit. I want that the "controllers" behave in a "bring your own component" way. This means that they would have a method returning the respective QML file for their controller. That shouldn't be to hard to do, it's basically biding a Loader to a method of the Factory/Manager saying the file with the component to load into a placeholder.
But the problem is: how can this newly created component and this newly created controller know and talk to each other? This is something I did before with QWidgets, just having pointers between the classes. Quite trivial.
I tried an architecture like this before for QWidgets, but seems to not be ideal for QML.
I made this drawing of what I would ultimately like to happen:
This architecture allows for a very trivial plugin system (at least in the QWidgets world) and I would very much like to keep that. Not a massive singleton and account for every possible action...
I'd appreciate ideas!
I think this is actually very easy, if you return a QQuickItem from the C++ side. If you do so you can create it with a specific context, in which you can set your "specific hardware controller" as a property
QQmlComponent *qml_controller = new QQmlComponent(qengine, "some_file.qml");
QQmlContext *context = new QQmlContext(); //should probably give a pointer to owning object
context->setContextProperty("controller", pointer_to_hw_cont);
return qml_controller->create(context);
The Loader setSource method have additional parameter you could pass to provide initial value for some property. Something like this:
ComboBox {
model: controlerFactory.specificHWListModel
onCurrentTextChanged: {
var specificHWControler = controlerFactory.getObjectFor( currentText );
loader1.setSource(
specificHWControler.qml_file,
{ "controler": specificHWControler }
);
}
}
Loader {
id: loader1
}
The specificHWListModel cold be QStringList or some custom QAbstractListModel.
And getObjectForcould be just a invokable function.
Q_INVOKABLE QObject* getObjectFor(QString hwName);
The object returned from Q_INVOKABLE function will be managed by QQmlEngine by default if you don't set by the QQmlEngine::setObjectOwnership. Remember to register your SpecificHWControler class to QQmlEngine.
The qml_file SpecificView.ui.qml, should have property controler, and could be edited with Designer:
import SpecificHWControlerModule 1.0
Item {
property SpecificHWControler controler
}
https://doc.qt.io/qtcreator/quick-connections-backend.html

Mock a C++ class instantiated in QML

I am trying to write a plugin that contains some QML files and some C++ classes that provide lower-level functionalities and communicate with another application. They are used by the QML components.
I want to be able to manage the life time of these C++ objects from QML (i.e. they should be created when the QML file is loaded and destroyed when QML is destroyed), while still being able to mock the C++ objects.
I tried a few different approaches so far. Ideally, the result will be that I can use qmlscene on the QML file I want to edit and have a dummydata folder next to that file which contains the mock for the instantiated C++ class.
If I try that by using qmlRegisterType in a plugin class that inherits from QQmlExtensionPlugin (similar to the example in https://qmlbook.github.io/ch17-extensions/extensions.html), and I pass the resulting library to qmlscene, the QML file will not use the mock, but instantiate a C++ object instead. This means that sometimes, I need to start up a fair bit of logic to get some mocked data into my QML file.
It seems like the example in the "QML Book" suggests to completely design the QML component with a mock before introducing any C++ to QML. Is there a way to do that more sustainable? I guess, I could avoid using qmlRegisterType for a C++ class that I want to mock for a while, by commenting out the according line, but I would like to not have to do that.
The other approach I tried was using QQMLContext::setContextProperty from a central C++ controller class. That enables me to pass the C++ object to QML from C++ and also use the dummydata, however the object's lifetime will not be managed by the QML component, but from C++. Also, each class should potentially be instantiated multiple times and connecting signals properly is pretty error-prone. This is what I found so far:
auto proxy = std::make_shared<Proxy>();
//make `proxy` object known in QML realm
_qmlEngine.rootContext()->setContextProperty("proxy", proxy.get());
connect(&_qmlEngine, &QQmlApplicationEngine::objectCreated,
[&proxy](QObject *object, const QUrl &url) {
if (url == QUrl("qrc:/imports/Common/TestWindow.qml")) {
// make sure the proxy is not destroyed when leaving scope of this function
connect(qobject_cast<QQuickWindow *>(object),
&QWindow::visibilityChanged, // as a dirty workaround for missing QWindow::closing signal
[proxy]() mutable { proxy.reset(); }); // delete proxy when closing TestWindow
}
});
_qmlEngine.load(QUrl("qrc:/imports/Common/TestWindow.qml"));
Is there a "comfortable" way to mock data instantiated in QML and originally coming from C++, or is there at least a good way to attach the life time of such a C++ object to the life time of the QML object?
The way I solved this issue is as follows:
The actual production application will use a C++ plugin, containing only C++ files and no QML.
For mocking, there is a QML module with the same name as the C++ plugin, containing QML files which provide the same interface as the equivalent C++ classes. This module is passed to qmlscene in addition to the general QML includes.
If the C++ class header looks like this:
class Proxy : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(int foo)
Q_INVOKABLE void start();
signals:
void started();
}
And this class is made available to QML like this:
qmlRegisterType<Proxy>("Logic", 1, 0, "Proxy");
The QML mock (in file Proxy.qml) can look like this:
import QtQml 2.12
QtObject {
signal started()
property var foo: 42
function start() { console.log("start") }
}
And be importable in QML with a qmldir file that looks like this:
module Logic
Proxy 1.0 Proxy.qml
The final call to qmlscene would be
qmlscene [path/to/prototype/qml] -I [path/to/folder/containing/proxy/mock/]

How to get a valid instance of a QQuickItem on C++ side

Alright. I have searched a lot but haven't got a good solution yet. I am new to Qt. I have a class which is a QQuickItem like so,
class MyQuickItemClass : public QQuickItem
{
Q_OBJECT
SetInfo(SomeCppClass object)
};
I do a qmlRegisterType in my main.cpp to register it on the qml side like this,
qmlRegisterType< MyQuickItemClass >("MyQuickItemClass", 1, 0, "MyQuickItemClass");
All fine till here. But -> I want to set an object instance & some properties in MyQuickItemClass which some C++ logic in it as well & then pass the MyQuickItemClass object to qml. Or, get a valid instance of MyQuickItemClass from Qml. How can I get a vlid instance MyQuickItemClass object instance from QML on C++ side in main.cpp ?
I tried doing the following learning from the link here. But this technique creates two separate objects of MyQuickItemClass. One from QML, & one from c++ side. Hence does not work for me.
Following is how I am trying to do this after lot of searching.
int main(int argc, char *argv[])
{
qmlRegisterType< MyQuickItemClass >("MyQuickItemClass", 1, 0, "MyQuickItemClass");
QQmlApplicationEngine engine;
SomeCppClass someCppClassObject;
someCppClassObject.updateSomething();
MyQuickItemClass myquickItemObject;
myquickItemObject.SetInfo(someCppClassObject);
engine.rootContext()->setContextProperty("myquickItemObject", &myquickItemObject);
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
return app.exec();
}
But, doing the above gets the constructor of MyQuickItemClass called twice. Once from cpp side when I created an object, and once from qml side. Verified this by placing a breakpoint in the constructor of MyQuickItemClass as well. As a result, someCppClassObject that I had set is null inside MyQuickItemClass when program runs. Because qml has made the final call to MyQuickItemClass to instantiate, thusly ignoring the MyQuickItemClass object that I created in main.cpp.
Here is my qml code for MyQuickItemClass:
import QtQuick 2.5
import MyQuickItemClass 1.0
ParentContainerItem {
id: parentItem
color: "black"
MyQuickItemClass {
id: myQuickItemID
visible: true
objectName: "myQuickItem"
property bool someProperty1: false
property bool someProperty2: true
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
}
//Other qml components
}
And this is the C++ class whose object needs to be set into MyQuickItemClass.
SomeCppClass {
//Pure C++ class. No Qt
}
Please note that I need to keep MyQuickItemClass derived from QQuickItem. Please suggest...
Generally it is a good idea to avoid accessing QML instantiated objects from outside as most of the access methods generated a dependency from C++ toward QML, restricting the way the QML tree is done.
E.g. requiring certain objects to exist at certain point in times, having specific objectName values, etc.
It is better to either "register" the object from QML side by calling a method on an exposed C++ object/API or to make the QML instantiate object register itself from within its own C++ code.
The latter is obviously inherently automatic, i.e. each instance of such a class would do that, while the former puts it at the discretion of the QML code which of the created instances it wants to make known.
Doing the following from a suggestion in discussion here solves the issue & gets a valid object to the QuickItem qml file
QQuickItem *myItem = engine.rootObjects()[0]->findChild<QQuickItem *>("myQuickItem");

Clearing WebView Cache from withing QML

I open website with QDeclarativeView and use JavaScript to load next pages in same view.
After each website loaded, my program occupy 20mb more of memory. How do i clean the cache or otherwise release the memory after new website is loaded?
I tried:
decView->engine()->rootContext()->setContextProperty("myEngine", decView->engine());
and then in qml
myEngine.clearComponentCache()
but i get
TypeError: Result of expression 'myEngine.clearComponentCache' [undefined] is not a function.
What i should do?
EDIT: here is what i got sofar:
aws.cpp
void Aws::openQMLWindowSlot(){
QDeclarativeView *decView= new QDeclarativeView();
decView->engine()->rootContext()->setContextProperty("myAws",this);
decView->setSource(QUrl("qrc:/inc/firstqml.qml"));
decView->show();
}
void Aws::clearCacheQMLSlot(){
//HERE I GOT PROBLEM
}
firstqml.qml
import QtQuick 1.1
import QtWebKit 1.0
WebView {
id: webView
objectName: "myWebView"
url:"http://example.com"
onLoadFinished: {myAws.clearCacheQMLSlot();}
}
There two reasons why your code doesn't work as intended. First, to be able to access slots and invokable methods of QObject descendants, you have to register them:
qmlRegisterType<QDeclarativeEngine>("MyApp", 1, 0, "QDeclarativeEngine");
And second, QDeclarativeEngine::clearComponentCache is neither a slot nor an invokable method, so it would still not work. It is simply impossible to call normal C++ methods from QML.
What you actually have to do is to implement an own QObject based class wrapping the call to QDeclarativeEngine::clearComponentCache in a slot, registering the class like above, set an instance of that class as an context property like you did with the declarative engine and finally call the slot from QML.