Unable to hold QQmlContext in std::shared_ptr - c++

I noticed some quite strange behavior when working with Qt 5.15/C++.
I was working on a small application when I wanted to set a context property for my root context.
My application is a QtQuick app which uses QQmlApplicationEngine instead of QQuickView (which was, for whatever reason, the default when creating a Qt Quick app):
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
// ...
QStringList entryList { "String1", "String2", "String3" };
Then, when assigning the string list as a context property for my root context, I wanted to use STL shared pointers instead of raw pointers:
std::shared_ptr<QQmlContext> context = std::make_shared<QQmlContext>(engine.rootContext());
if (!context) {
qDebug() << "Failed to get root context. exiting.";
exit(-1);
}
context->setContextProperty("entryList", QVariant::fromValue(entryList));
My entry list will be empty inside QML.
However, when I use raw pointers:
QQmlContext* context = engine.rootContext();
// ...
everything works just fine and the entryList var is filled inside QML.
Is there a logical behaviour behind this which I don't yet understand?
By the way, when using QSharedPointers instead of shared_ptr, The list will be filled, however I'm getting a debugger exception on exit:
Exception at 0x7ff8022f2933, code: 0xc0000005: read access violation at: 0xffffffffffffffff, flags=0x0 (first chance)

Qt clearly indicates when the developer must manage the memory of an object, if it does not indicate it then it must be assumed that Qt will eliminate it when necessary.
In this particular case the QQmlContext is created by QQmlApplicationEngine so it is the responsibility of that class to manage its memory. For example, a different case is QNetworkAccessManager is that in the docs it indicates that the developer must handle the memory of the created QNetworkReply(docs):
Note: After the request has finished, it is the responsibility of the user to delete the QNetworkReply object at an appropriate time. Do not directly delete it inside the slot connected to finished(). You can use the deleteLater() function.
The memory management that Qt is responsible for can be done through the QObject hierarchy or use its own pointers (QPointer, QSharedPointer, etc) but that is irrelevant for the developer since that is part of the private Qt API.

Related

Access to a QQmlApplicationEngine inside my custom class

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

Dynamically Loading QML from Memory Instead of a URL

Is it possible to dynamically load QML from memory instead of using a file? I've seen a lot of code that always needs a URL, like this:
QGuiApplication app(argc, argv);
QQmlEngine* engine = new QQmlEngine;
QQmlComponent component(engine, QUrl(QStringLiteral("qrc:/main.qml")));
// ignoring error checking (make sure component is ready, compiles w/o errors, etc)
QObject* object = component.create();
app.exec();
In the example above, main.qml will be loaded, and the magic of QML will ensure any types get resolved. In particular, Qt considers the name of the file to be the QML type, which is made available to any other QML file within the same directory. So if main.qml used the type Simple, it would look for the file Simple.qml and use that as the type definition (http://doc.qt.io/qt-5/qtqml-documents-definetypes.html).
Now I'm going to get dangerous: I want to use in-memory QML instead of using a file. This is how it might work (please ignore bad design, memory leaks, etc. for the moment):
QByteArray SimpleQml()
{
return QByteArray("import QtQuick 2.7; Text {") +
"text: \"Hello, World!\"}";
}
QByteArray TextExampleQml()
{
return QByteArray("import QtQuick 2.7; Simple{") +
"color: \"Red\"}";
}
QObject* createObjectFromMemory(QByteArray qmlData, QQmlEngine* engine, QQmlComponent* componentOut)
{
componentOut = new QQmlComponent(engine);
// note this line: load data from memory (no URL specified)
componentOut->setData(qmlData, QUrl());
if (componentOut->status() != QQmlComponent::Status::Ready)
{
qDebug() << "Component status is not ready";
foreach(QQmlError err, componentOut->errors())
{
qDebug() << "Description: " << err.description();
qDebug() << err.toString();
}
qDebug() << componentOut->errorString();
return nullptr;
}
else
{
return component.create();
}
}
int main(int argc, char* argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
QQmlComponent* component = nullptr;
// works great: shows a small window with the text "Hello, World!"
QObject* object = createObjectFromMemory(SimpleQml(), view.engine(), component);
// problem: Simple is not a type
// QObject* object = createObjectFromMemory(TextExampleQml(), view.engine(), component);
// setContent() is marked as an internal Qt method (but it's public)
// see source for qmlscene for why I'm using it here
view.setContent(QUrl(), component, object);
view.show();
app.exec();
// don't forget to clean up (if you're not doing a demo snippet of code)
//delete object;
//delete component;
}
So SimpleQml() simply is a Text object with "Hello, World!" as the text. When createObjectFromMemory() is called with SimpleQml() as the data source, a small window appears with the text "Hello, World!".
I'm using QQuickView::setContent(), which is a function that is marked as internal in the Qt source, but it's used in qmlscene, and I'm trying to achieve a similar effect here (https://code.woboq.org/qt5/qtdeclarative/tools/qmlscene/main.cpp.html#main).
Obviously, the problem is when I try to call createObjectFromMemory() with TextExampleQml() as the data source. The QML engine has no idea that Simple should be a type--how can I tell QML about this type? I thought that qmlRegisterType (http://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterType) might be a good candidate, but I could not find a way to register a type that is coming from a chunk of QML sitting in memory. I'd like to say, "Hey, I have this chunk of QML code, and it's type is 'Simple'."
Let's assume I could easily predict all type dependencies and would be able to know the proper order to load QML memory chunks. Can I register the QML code returned by SimpleQml() as the type Simple (keeping it in memory, and not saving it to a file)? If anything, I'd like to understand how the QML engine does this under the hood.
I realize I'm breaking a golden rule: don't reach into QML from C++ (reach into C++ from QML instead). So if anyone has a JavaScript solution that's cleaner, I'm open to suggestions, but please remember I'm trying to load from memory, not from file(s).
Update:
As noted in the comments, there is a JavaScript function called createQmlObject() that creates a QML object from a string, which is extremely similar to what I'm doing in C++ above. However, this suffers the same problem--the type that createQmlObject creates is...well, it's not a type, it's an object. What I'd like is a way to register a string of in-memory QML as a QML type. So my question still stands.
One option would be to provide a custom QNetworkAccessManager to your QML engine and implement handling of a custom URL scheme via createRequest().
E.g. your implementation of createRequest() would check if the passed URL has your scheme, lets say `memory", if yes, it takes the rest of the URL to decide which function to call or which data to deliver.
For anything else it just calls the base implementation.
Your main QML file could then be loaded like this
QQuickView view;
view.setSource(QUrl("memory://main.qml"));
All URLs in QML are relative to the current file if not specified otherwise to a lookup for Simple should look for memory://Simple.qml

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!

Using QtScript outside my main form

I am using Qt5, and trying to learn on how to make an application scriptable.
For this I created a main window that contains some text edits, labels, etc. I then added an option called "script console" to that forms' menu in order for me to open a second form containing just a text edit and a button called "Evaluate".
What I was aiming at was being able to use that second form and through Qt script engine be able to set or get values from my main form, and generally be able to script various functions.
What I tried doing was set up the engine like this
scriptingconsole::scriptingconsole(QWidget *parent) :
QDialog(parent),
ui(new Ui::scriptingconsole)
{
ui->setupUi(this);
QScriptValue appContext = myScriptEngine.newQObject(parent);
myScriptEngine.globalObject().setProperty("app", appContext);
}
I don't get what I was expecting though.
If I try to evaluate the expression "app" I get null as an output.
This works fine if I use myScriptEngine.newQObject(parent) with an object inside the current class (if instead of parent I enter this), but I want to be able to access object in other classes too (hopefully all public slots that are used by my app in general).
Does anyone know what I am doing wrong here and how can I use my scripting console
class to access public slots from my main window?
What's wrong?
I guess it's because you didn't explicitly pass the pointer, which points to your main form, to the constructor of your scriptingconsole. That's why you got NULL as a result. (NULL is default, as you can see QWidget *parent = 0 in every QWidget constructor)
This happens if your object is not dynamically instantiated.
Solution
Dynamically allocate scriptingconsole in your main form:
scriptingconsole* myScriptConsole;
//...
myScriptConsole = new scriptingconsole(this);
// ^^^^
// pass the pointer which points to parent widget
The Qt documentation of QScriptEngine::newQObject says:
Creates a QtScript object that wraps the given QObject object, using the given ownership. The given options control various aspects of the interaction with the resulting script object.
http://qt-project.org/doc/qt-4.8/qscriptengine.html#newQObject
i.e. it wraps a QObject.. You are probably passing NULL to newQObject, for whatever reason. Try setting a breakpoint and evaluating the value of 'parent'.