Access to a QQmlApplicationEngine inside my custom class - c++

I am writing a Media Player in Qt using QML, I am making a Qlist of songs available in a directory and I want to send this list as a model to QML file, but I don't know how to access an engine declared in main inside my custom class Player.
So, there's my main.cpp
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
qmlRegisterType<Player>("io.qt.examples.player", 1, 0, "Player");
QQmlApplicationEngine engine;
Player player;
engine.rootContext()->setContextProperty("player", &player); // this works fine
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
Inside my Player class I want to set another property, and I have found this code to help:
QQmlContext *currentContext = QQmlEngine::contextForObject(this); // debugger says it's null
QQmlEngine *engine = currentContext->engine(); // Segmentation fault
I am getting a Segmentation fault error when debugger reaches this line, I have properly set a QStringList with names of files and want to do this:
engine->rootContext()->setContextProperty("listModel", QVariant::fromValue(files));
And I want to use this model there:
ListView {
id: listView
model: listModel
delegate: Rectangle {
Text {
text: modelData
}
}
}
But getting also a QML error: ReferenceError: listModel is not defined

I'm going to answer the question as best as I can without a complete example.
I am getting a Segmentation fault error when debugger reaches this line
The problem is likely that the type is not constructed by the engine. The docs say:
When the QQmlEngine instantiates a QObject, the context is set automatically.
So you probably need to set the context manually. Usually this is done by using the context from an object that you know was constructed by the engine:
QQmlEngine::setContextForObject(this, qmlContext(someQmlObject))
But getting also a QML error: ReferenceError: listModel is not defined
Again, I don't have access to a complete example so I can only guess, and my guess would be that you set the context property too late. Context properties should be set before the QML that uses them is loaded.
What I would suggest though, is that you don't set context properties, and instead create a proper model and register it as a type that can then be instantiated in QML.
For more information about the various approaches to C++ and QML integration, take a look at this.

Actually I have found the answer here, changing my approach and switching my QStringList model to a Q_PROPERTY

Related

qml read variables from c++?

i am trying to set applicationwindow {} size for my android apk, so i wish to read the values from cpp file:
main.cpp:
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QScreen *screen = QApplication::screens().at(0);
QVariant sz_width = screen->availableSize().width();
QVariant sz_height = screen->availableSize().height();
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
then from qml file to read it (main.qml):
ApplicationWindow {
id: mainWindow
visible: true
width: sz_width
height: sz_height
}
this is in order to easly manipulate with all object sizes later on in qml, so basicaly for example i use font size by mainWindow*0.5 so i can have proper font size for every app resolution, but it only works if i realy set up the variables width and height...
Maybe this solution is a but "morbid", but however I would like to do it this way if you can help me with proper syntax...
Thank you
To quickly make C++ values visible in QML you can set them as a context property:
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("screenWidth", sz_width);
engine.rootContext()->setContextProperty("screenHeight", sz_height);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
After this the variables are available in QML by the quoted names ("screenWidth" and "screenHeight") (these could also match the C++ variable name if you prefer).
QSize type is also recognized by QML, so you could just set the size as one variable.
engine.rootContext()->setContextProperty("screenSize", screen->availableSize());
Also in this case the information you're looking for is already available in QML... check the Screen attached object and also the Qt.application.screens object/property for a list of available screens.
ADDED:
Since the linked documentation doesn't mention it directly, it should be noted that context property variables set in this way have no change notifier signals. Therefore they do not automatically update in QML, unlike other "bindable" properties. The only way to get QML to update the value automatically is to set the context property again (or create some signal which QML can connect to and cause it to re-read the value).
I can't find where exactly this is mentioned in the Qt docs, but the QQmlContext page provides a (subtle) clue:
The context properties are defined and updated by calling QQmlContext::setContextProperty().

It is possible connect a QML Object existing signal in C++ QObject::connect()?

The QML TreeView has an signal named: doubleClicked(QModelIndex)
ref: https://doc.qt.io/qt-5.10/qml-qtquick-controls-treeview.html#doubleClicked-signal
Its possible connect that existing signal in a C++ QObject::connect() ??
I tried this:
QQmlApplicationEngine engine;
QObject *myTreeMenu = engine.rootObjects().at(0)->findChild<QObject*>("myTreeMenu");
connect(myTreeMenu , SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotModelClicked(QModelIndex)));
But i receive this return error:
QObject::connect: No such signal TreeView_QMLTYPE_63_QML_68::doubleClicked(QModelIndex) in '...'
QObject::connect: (sender name: 'myTreeMenu ')
The documentation for Qt 5.11 explains why this is not the best way to work with C++ and QML. I would suggest you instead expose a slot or Q_INVOKABLE in your C++ object and call that in the onDoubleClicked handler:
Q_INVOKABLE void doStuff();
in QML:
onDoubleClicked: cppObject.doStuff()
The documentation has a "before and after" example demonstrating this; here's the bulk of it:
With this approach, references to objects are "pulled" from QML. The
problem with this is that the C++ logic layer depends on the QML
presentation layer. If we were to refactor the QML in such a way that
the objectName changes, or some other change breaks the ability for
the C++ to find the QML object, our workflow becomes much more
complicated and tedious.
Refactoring QML is a lot easier than refactoring C++, so in order to
make maintenance pain-free, we should strive to keep C++ types unaware
of QML as much as possible. This can be achieved by "pushing"
references to C++ types into QML:
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
Backend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("backend", &backend);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
The QML then calls the C++ slot directly:
import QtQuick 2.11
import QtQuick.Controls 2.4
Page {
Button {
text: qsTr("Restore default settings")
onClicked: backend.restoreDefaults()
}
}
With this approach, the C++ remains unchanged in the event that the
QML needs to be refactored in the future.
In the example above, we set a context property on the root context to
expose the C++ object to QML. This means that the property is
available to every component loaded by the engine. Context properties
are useful for objects that must be available as soon as the QML is
loaded and cannot be instantiated in QML.
Integrating QML and C++ demonstrates an alternative approach where QML
is made aware of a C++ type so that it can instantiate it itself.

How to get valid class instance after doing a qmlRegisterType?

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.

C++ signals and accessing QtQuick items

I am trying to write an QtQuick program which works as an intelligent interface between user and a few CLI applications. I have implemented QtQuick + JavaScript application.
A QtQuick butten emits signal that is listened by C++ layer. So far everything works well.
However, in my C++ slot function I need to write to a certain Item in QtQuick application. This Item is an TextArea which serves as Log output of CLI applications. These CLI applications are run from the slot function in C++. I store their output into a variable, and I want to show output of this variable in this Log output TextArea.
I tried a lot of things, but I didn't find the right way to do that
I'd a similar problem.
This is how I solved it.
In C++ I created a class that handles the command with a QProcess (and I expose the class to QML), which attach the readyToRead signal to a C++ function in my exposed class, this function emits another signal showOutput with the output text.
With this information I just connect my new signal to a javascript function in qml:
cppExposed.showOutput.connect(jsFunction);
And in the javascript function I just append the text
function jsFunction(output) {
OutputTextArea.text += output;
}
To expose C++ properties to QML you can have a look at the documentation here: http://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html
I think the easiest way for you is to create an object of your cpp class, and set as a context property in your main.cpp before load your main.qml:
Something like this:
QQmlApplicationEngine engine;
ProcessHandler procHandler;
engine.rootContext()->setContextProperty("procHandler", &procHandler);
Now you can access your object direct from QML, and you can connect signals
procHandler.showOutput.connect(jsFunction)
And in your C++ class don't forget to connect with the process ReadyToReady signal and emit your own signal with the data:
void readyToRead() {
emit showOutput(m_proc.readAllStandardOutput());
}
UPDATE:
Your property should be set before load the QML file by the engine:
AnalyzeSignal analyzeSignal;
engine.rootContext()->setContextProperty("analyzeSignal", &analyzeSignal);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
And I don't think you need that writeToLogOutput to be a property (and it has some syntax error btw), it's a signal method, right? So it's automatically available to QML as a signal, not a property.
DON'T create a new QQMLContext.
In this line what you are doing is creating a new QQMLContext
QQmlContext *context = new QQmlContext(engine.rootContext());
context->setContextProperty("analyzeSignal", &analyzeSignal);
This won't work, as you are setting the property to the newly created context, not to the original root context.
My final solution of main function looks like this:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
AnalyzeSignal analyzeSignal;
QQmlContext *context = engine.rootContext();
context->setContextProperty("analyzeSignal", &analyzeSignal);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
// Registering slot onto a signal
QObject *win = engine.rootObjects()[0];
QObject *item = win->findChild<QObject*>("myButton");
QObject::connect(item, SIGNAL(doAnalyzeSignal(QString,QString,QString,QString)), &analyzeSignal, SLOT(cppSlot(QString,QString,QString,QString)));
return app.exec();
}
And this works!

C++/QML: How to define and handle multiple contexts for dynamically created components?

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.