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/]
Related
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
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");
I am using Qt 5.7 on ios & android. I use call to qmlRegisterType to instantiate MyClass derived from QQuickItem view.
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClass");
QQmlApplicationEngine engine;
QQmlContext* ctx = engine.rootContext();
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
return app.exec();
}
How can I get a valid object of MyClass back from qml ?
You can get access to the tree of objects instantiated by the QML code through the engine's root object(s), see QQmlApplication::rootObjects()
You can then traverse the QObject tree to find the object(s) you are looking for.
However, accessing objects instantiated in QML on the C++ side is very often just a hack for something that can be done in a better way.
The general rule of thumb is to avoid C++ code depending on QML code, i.e. avoid making the rather static C++ side depend on the way more dynamic QML side.
Prefer letting the C++ side provide data and functionality and letting the QML side consume that data and trigger/call the C++ functions.
In your code you registered MyClass QML custom object into the QML Objects Library. That would allow you instantiate your QML component MyClass in your QML documents like this:
import MyClass.1.0
Item {
.....
MyClass{
}
}
So registering QML objects will give you the possibility to instantiate your QML Objects like the one you mentioned in you sample code.
So if you want, to access any object from your QML "Scene" in any of your C++ classes you got load the Object Scene, and crawl through their children. Refer to this documentation:
Interacting with QML Objects from C++
And you should be more than fine.
One more thing: I think the most common "first time ever" need when reading QML objects from C++ is to read a property, so in the link there's this section: "Accessing Members of a QML Object Type from C++". Go right there for a start.
Is it possible to load an .ui class generated by uic, dynamically by class name? I need to decide which UI class to load dynamically. I do not have that information at compile time. I do not want to use QUiLoader. Instead, I want to combine the direct approach here with QMetaType object instantiation by string.
I.e.
add the UI file to FORMS in project file,
declare the UI classes for QMetaType usage Q_DECLARE_METATYPE(Ui::planar_break) or Q_DECLARE_METATYPE(Ui_planar_break)
then form a string class name dynamically depending on user action: "Ui::planar_break" or "Ui_planar_break"
and invoke the below function to get the UI widget pointer for usage?
QWidget* initiateClassByName(QString name){
int id = QMetaType::type(name.toLatin1());
QWidget* widget=nullptr;
if (id != QMetaType::UnknownType) {
widget=static_cast< QWidget* > (QMetaType::create(id));
//QMetaType::destroy(id, myClassPtr);
//myClassPtr = 0;
}
return widget;
}
I am trying to increase the performance, compared to loading a dozen or so UI files (stored in Qt resource files) dynamically every time a specific dialog is instantiated. When I do this I seem to get a QMetaType::UnknownType every time. Ideas? Thanks.
(Hm, not sure why my function wasn't showing as code block here, until I made it a quotation.)
uic creates C++ code. If you really want to dynamically create a widget/dialog out of an xml file at runtime, you need to use the Qt Ui Tools. The class QUiLoader might be what you are looking for. If you do this, you can query the created QWidget through QWidget::findChild You can interact with the UI items through QObject::findChild(), provided you give your widgets distinct and meaningful object names.
Essentially, based on discussion had in #qt irc channel on Freenode I think what I am asking is actually not feasible.
My understanding is that even if I could get the C++ headers compiled and the classes registered using perhaps also qRegisterMetaType(), and perhaps even get them instantiated using QMetaType, to actually get the UI built I would have to call setupUi() on the instance.
Since UI files do not implement a common interface that includes setupUi, and I don't know compile-time which class I am instantiating, calling setupUi becomes impossible.
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.