I very much want to subclass a Qt QQuickItem class for use in QML, therefore giving it a lot of C++ logic behind the scenes. However, since QML will instantiate it and own it, not the C++ side, I do not know how (if it is possible) to pass pointers or connections between this new object and other C++ objects in the system. Is this possible?
You can register a global QML object in you main.cpp which poins to an arbitrary QObject.
Brain *brain = new Brain();
QQmlApplicationEngine engine;
QQmlContext *context = engine.rootContext();
context->setContextProperty("brain", brain);
Now you have brain available globally in QML. You can pass it to a custom component, for example
VisibleComponent {
id: vico1
width: 300
height: 300
Component.onCompleted: {
vico1.setLogic(brain)
}
}
This requires a Q_INVOKABLE void setLogic(Brain* brain); in VisibleComponent.
I have a running example project of this code, let me know if you need it.
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
Having used Qt for C++ applications for quite some time now I wanted to do my next project using QML.
I now have the following scenario:
red: QML files and QML engine
blue: C++ classes
Now I want to be able to call C++ functions from my QML files (green arrows).
Content.qml needs to read properties from WifiManager
LiveField.qml and GameField.qml need to show / hide the corresponding C++ views
I used C++ for the views because of some heavy 3D stuff which I'm not that familiar with in QML (I only used QML for the UI menu).
I'd rather not create the C++ classes from within my QML code using qmlRegisterType since I need to do some initializing in my C++ code.
What is the best way to solve my problem?
C++ objects are typically shared using QQmlContext::setContextProperty. You can find more information about QQmlContext here. This makes any object (or value) that you put in the context widely available.
Two words of caution though:
Use your context properties only in high-level components, and not in reusable ones, as this will create a direct dependency to these values
Be careful to load your GUI after you set all your context properties, to make sure they are accessible by you UI from the start.
C++ side
#include "wifimanager.h"
// That one is required
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlApplicationEngine>
void main() {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
WifiManager wifi;
engine.rootContext().setContextProperty("wifiManager", &wifi);
engine.load(QUrl("qrc:/main.qml"));
return app.exec();
}
You can then use wifiManager on the QML side, along with its slots, Q_PROPERTYies, and signals
Reading a Q_PROPERTY
As with regular QML, you can now bind and read your object's properties.
Read: var value = wifiManager.value
Bind: someProperty: wifiManager.value
Any QML binding will be re-evaluated automatically whenever the value changes, as long as you emit the associated NOTIFY signal. For example:
Q_PROPERTY(QString ssid READ ssid WRITE setSsid NOTIFY ssidChanged)
Text {
// Calls the READ getter of your Q_PROPERTY
// Will automatically update whenever the SSID changes
text: wifiManager.ssid
}
Writing a Q_PROPERTY
As easy as reading the value, you can write to it by doing wifiManager.ssid = xxx
Button {
text: "Reset SSID"
onClicked: {
// Calls the WRITE setter of your Q_PROPERTY
wifiManager.ssid = ""
}
}
Handling signals
Signals can be handled with the Connections object. As with any QML object, you have to prefix your signal's name with on and a capital letter. Which gives onWifiConnected: {} for signal void wifiConnected();
Connections {
target: wifiManager
// Handle `wifiConnected` signal
onWifiConnected: {
console.log("Connected!")
// If your `wifiConnected` signal has an argument named `ip`
// it will be available here under the same name
console.log("My IP is", ip)
}
}
Calling slots
Slots and Q_INVOKABLEs are accessible as any other functions in javascript. So you can call wifiManager.disconnect()
Button {
text: "disconnect"
onClicked: {
// Calls the `disconnect` slot or Q_INVOKABLE
wifiManager.disconnect()
}
}
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.
Basically my situation is like this:
I've got a class that extends QQuickView and that exposes certain objects from C++ to QML by setting context properties. The views that are shown are created from QML and are all different istances of the same custom made component; new views are created when certain events occur, and when that happens the existing views should show the objects that were initially assigned to them in the C++ side, and the new ones should show the things assigned to them.
So, in the C++ side I've got something like this:
WindowManager::WindowManager(QQuickView *parent) :
QQuickView(parent)
{
// Setting the source file to use
this->setSource(QUrl("qrc:/qml/main.qml"));
// Exposing the istance of this class to QML for later use
this->rootContext()->setContextProperty("qquickView", this);
// Calling the method that will create dynamically a new view that will be child of main.qml; the parameter is not important, just a random number to start with
this->prepareNewView(3)
this->showFullScreen();
}
WindowManager::prepareNewView(int stuffId)
{
MyDatabase db;
// Getting something to show in QML from somewhere based on the parameter received
SomeStuff stuff = db.getStuff(stuffId)
// Exposing the object I need to show in QML
this->rootContext()->setContextProperty("someStuff", stuff);
QObject *object = this->rootObject();
// Here I'm invoking a function from main.qml that will add a new view dynamically
QMetaObject::invokeMethod(object, "addView");
}
Now, in the QML side I've got a main file like this:
// main.qml
Rectangle {
id: mainWindow
width: 1000
height: 1000
// This function adds a component to mainWindow
function addView()
{
// Creating the component from my custom made component
var component = Qt.createComponent("MyComponent.qml");
// Creating an istance of that component as a child of mainWindow
var newView = component.createObject(mainWindow);
// ... Now I would be doing something with this new view, like connecting signals to slots and such
}
}
Then I've got my custom component, which is the view that will be created dynamically:
// MyComponent.qml
Rectangle {
id: customComponent
// Here I would be using the object I exposed from the C++ side
x: someStuff.x
y: someStuff.y
width: someStuff.width
height: someStuff.height
// Here I'm creating a MouseArea so that clicking this component will cause the creation of another view, that will have to show diffrent things since the parameter I'm passing should be different from the starting parameter passed in the constructor of WindowManager
MouseArea {
anchors.fill: parent
onClicked: qquickView.prepareNewView(Math.random())
}
}
Now, with everything as it is, at first it will show "the stuff" with id 3, that was exposed as a context property of the main context.
But then, if I click on the MouseArea, assuming that an id other than 3 will be passed, a new context property with the same name will be exposed, causing the override of the old property. This means that the first view will now show "the stuff" just exposed, not "the stuff" based from the stuffId equals to 3, while what I need is the first view to keep showing what it was supposed to show ("the stuff" with id = 3), and any other view that will come later the things corresponding to their ids.
This happens because I'm defining a property in the context that is common to every component, while I should be defining a property that is visible ONLY by the new istance of the component that is being created dynamically. But how do I do that?
In the documentation I read that it's possibile to create a component directly from C++ and defining the context that it should use... something like this (snippet taken from here):
QQmlEngine engine;
QStringListModel modelData;
QQmlContext *context = new QQmlContext(engine.rootContext());
context->setContextProperty("myModel", &modelData);
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\nListView { model: myModel }", QUrl());
QObject *window = component.create(context);
I think that this would work for what I intend to do. Whenever I create a new view from C++ (caused by the click on the MouseArea) I create a new context with "someStuff" as its property, so that each view has its own "stuff"... but I need to have access to the newly created view from QML, because after I create it in the addView() function inside main.qml I access the view in order to do certain thins (not important what exactly), and if I create the istance of the component from C++ I don't know how to access it from QML... is there a way maybe to pass the component from C++ to QML in order to access it?
I'm out of ideas on how to resolve this or to find another way to have dynamically created views with custom contents all visible at the same time... any suggestions are appreciated.
I actually found out that it is possible (and easy) to directly pass a component created in C++ to QML.
So right now, I modified the code pretty much like this:
WindowManager::prepareNewView(int stuffId)
{
MyDatabase db;
// Getting something to show in QML from somewhere based on the parameter received
SomeStuff stuff = db.getStuff(stuffId)
// Creating the new context, based on the global one
QQmlContext *context = new QQmlContext(this->rootContext());
// Exposing the object I need to show in QML to the new context
context ->setContextProperty("someStuff", stuff);
// Creating the component
QQmlComponent component(this->engine(), QUrl("qrc:/qml/MyComponent.qml"));
// Creating the istance of the new component using the new context
QQuickItem *newView = qobject_cast<QQuickItem*>(component.create(context));
// Getting the root component (the Rectangle with it mainWindow)
QObject *object = this->rootObject();
// Manually setting the new component as a child of mainWIndow
newView->setParentItem(qobject_cast<QQuickItem*>(object));
// Invoking the QML that will connect the events of the new window, while passing the component created above as QVariant
QMetaObject::invokeMethod(object, "addView", Q_ARG(QVariant, QVariant::fromValue(newView)));
}
In QML the function in the main.qml is now like this:
// Function called from C++; the param "newView" is the last component added
function addView(newView)
{
// ... Here I would use the new view to connect signals to slots and such as if I created "newView" directly in QML
}
So I managed not to change the code too much after all.
I think you can pass your component instance (QObject) by setting an object as a context property as you did in your code.
class ViewInstance : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE QObject* getCurrentViewInstance() {
...
QObject *window = component.create(context);
return window;
}
};
int main(int argc, char *argv[]) {
...
QQuickView view;
ViewInstance data;
view.rootContext()->setContextProperty("viewInstance", &data);
}
Then, in your qml, you can get the component instance by calling viewInstance.getCurrentViewInstance(). Hope this helps.