QQmlApplicationEngine not completely unloading qml components - qt

I'm currently loading my main.qml using QQmlApplicationEngine and works fine, and then I want to switch to main2.qml (without calling quit() on my QQmlApplicationEngine since that triggers QCoreApplication::exit() which will exit my application).
So I just delete my engine, make a new one and set the context properties again (not the same context properties for main.qml slightly different), and it loads fine. Then I switch back to main.qml (load main.qml again) and I start getting warnings like
qrc:/qml/...: Cannot read property of null
That particular property is null in the context of main.qml so this is correct, but it is not null in the context of main2.qml. But my question is why don't I get the warning when I load main.qml the first time? I only seem to get the warning if I load main.qml after I have loaded main2.qml.
Your help is appreciated.
EDIT: Here is a simple example code
QSharedPointer<QQmlApplicationEngine> m_engine;
QQmlContext* m_ctxt;
void loadEngine(int window){
m_engine->clearComponentCache();
m_engine.reset(new QQmlApplicationEngine, &QObject::deleteLater);
m_ctxt = m_engine->rootContext();
m_ctxt->setParent(m_engine.get());
QVector<QQmlContext::PropertyPair> qmlProperties;
qmlProperties.push_back(QQmlContext::PropertyPair{"object", QVariant::fromValue(object)});
if(window == 1){
qmlProperties.push_back(QQmlContext::PropertyPair{"object1", QVariant::fromValue(object1)});
// add more context properties
m_ctxt->setContextProperties(qmlProperties);
m_engine->load(QUrl(QLatin1String("qrc:/qml/main.qml")));
}
else{
qmlProperties.push_back(QQmlContext::PropertyPair{"object2", QVariant::fromValue(object2)});
// add more context properties
m_ctxt->setContextProperties(qmlProperties);
m_engine->load(QUrl(QLatin1String("qrc:/qml/main2.qml")));
}
}

I'd like to recommend you use a QML Loader component to change current viewable view. On this page you can find a few examples how to use Loader(s) https://doc.qt.io/qt-5/qml-qtquick-loader.html.
In this case you have to provide both context properties for "object1" and "object2".

Related

Understanding signals, slot order, and parent widgets

So I have a situation where I can't get a couple slots to fire in the order that I would like them to.
The basic set-up is that I have a Mainwindow with a statusbar that needs to get updated based on a signal from a child widget (SearchWidget). When the "Go" button is clicked on the child widget, I would like it to update the status bar to say "Searching..." and then perform the actual database search. However, I can only get the updateStatusBar slot to trigger AFTER the search is complete and displayed in a tablewidget. I have tried re-arranging the connections to the appropriate order, I have tried a separate function that emits the signal for the statusbar and then the signal for the search, but nothing seems to work. The search always executes first and the statusbar doesn't change until after that is complete.
I'm a newbie a this, but I'm guessing maybe the issue has something to do with the parent-child relationship between the mainwindow and the widget? Perhaps slots within the same widget are prioritized in some way? See basic code below.
Mainwindow class:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
initMembers();
initUI();
connect(searchWidget, SIGNAL(searchingStatus(QString)), this, SLOT(updateStatusBar(QString)));
}
void MainWindow::initMembers()
{
tabWidget = new QTabWidget(this);
searchWidget = new SearchWidget(this);
saleWidget = new SaleWidget(this);
statusBar = new QStatusBar();
setCentralWidget(tabWidget);
setStatusBar(statusBar);
}
void MainWindow::initUI()
{
tabWidget->addTab(searchWidget, "Search");
tabWidget->addTab(saleWidget, "Sale Data");
}
void MainWindow::updateStatusBar(QString status)
{
statusBar->showMessage(status);
}
SearchWidget class:
SearchWidget::SearchWidget(QWidget *parent) : QWidget(parent)
{
connect(goButton, SIGNAL(clicked), this, SLOT(buildQuery()));
}
void SearchWidget::buildQuery()
{
emit searchingStatus("Searching...");
//builds sql query
}
Any enlightening info would be much appreciated!
I suspect that it's not an ordering issue, but the fact that the SQL query is blocking the main GUI event loop: try inserting a call to QApplication::processEvents() after your emit searchStatus(...) call. If I'm correct, you should see the status bar update before the database search completes.
However, because you're still blocking the event loop, your GUI will still freeze while the DB call executes, which isn't great. You can eliminate this by running the query on a different thread (one of the simplest ways is via http://doc.qt.io/qt-5/qtconcurrent.html#run), but beware you then have to worry about concurrency issues (e.g., now you can click the go button lots of times in a row...).

How can I pass a QML object reference into Qt C++?

I am attempting to create something like jQuery's autocomplete as a widget in QML and Qt C++. Toward that end, I created a C++ AutoCompleteListener child of QObject and then register it with:
qmlRegisterType<AutoCompleteListener>(
"foo.AutoCompleteListener",0,1,"AutoCompleteListener");
Then, I instantiate the listener and the AutoCompleteForm like:
import QtQuick 2.5
import com.foo.AutoCompleteListener 0.1
Item {
AutoCompleteForm { id: autocomplete_form }
AutoCompleteListener { id: listener }
}
How can I pass a reference to the QML object AutoCompleteForm into AutoCompleteListener?
I tried passing the autocomplete_form field into:
Q_INVOKABLE void set_autocomplete_form(QQmlComponent *autocomplete_form);
on the onCompleted signal:
Item {
AutoCompleteForm {
id: autocomplete_form
Component.onCompleted: {
console.log("AutoCompleteForm completed");
listener.set_autocomplete_form(autocomplete_form);
}
}
AutoCompleteListener {
id: listener
Component.onCompleted: {
console.log("AutoCompleteListener completed");
}
}
}
However, the reference is a nullptr even though both AutoCompleteListener and AutoCompleteForm have been instantiated:
Instantiating AutoCompleteListener and parent is QObject(0x0)
qml: AutoCompleteListener completed
qml: AutoCompleteForm completed
Setting autocomplete_form = QObject(0x0)
How can I get a reference to the AutoCompleteForm or AutoCompleteListener's QML parent? I want to avoid crawling down the entire QML hierarchy with something like:
QObject* f = mView.rootObject();->findChild<QObject *>("AutoCompleteForm");
I plan to support having multiple AutoComplete widgets instantiated in parallel so a relative path (../AutoCompleteForm) to manipulate the QML objects seems better than having to crawl through the tree.
How can I pass a QML object reference into Qt C++?
You can't, the language was not designed for that. But you can get references from within C++ using findChild and findChildren. But read below for your real solution.
Also, I think your question is about an XY Problem. You have a problem X and you think Y solves it, so you ask for Y.
Correct solution for your original problem:
I am attempting to create something like jQuery's autocomplete as a
widget in QML and Qt C++.
In order to solve your real problem you need to use property bindings correctly. QML is a declarative language and self obsession with imperative programming makes it difficult to be straightforward.
Use this pattern for QML:
AutocompleteForm{
id: form
text: "Search here..."
suggestedTerms: helper.results
}
AutoCompleteHelper{
id: helper
searchFor: form.text
}
And for C++ implement
class AutoCompleteHelper : public QObject
{
Q_OBJECT
Q_PROPERTY(QString searchFor READ searchFor WRITE setSearchFor NOTIFY searchForChanged)
Q_PROPERTY(QStringList results READ results NOTIFY resultsChanged)
public:
AutoCompleteHelper() {}
virtual ~AutoCompleteHelper() {}
QString searchFor() const { return m_searchFor; }
QStringList results() const { return m_results; }
public slots:
void setSearchFor(QString searchFor)
{
if (m_searchFor == searchFor)
return;
m_searchFor = searchFor;
emit searchForChanged();
// 1. Search for it...
// 2. Some time later fill m_results
// 3. Then: emit resultsChanged()
}
signals:
void searchForChanged();
void resultsChanged();
private:
QString m_searchFor;
QStringList m_results;
};
Then you will see it magically works, because as soon as you change the form.text the binding sets the value in helper.searchFor, which then immediately fires the C++ slot where you can react even instantaneously, and in C++ the emission of resultsChanged() magically fills form.suggestedTerms.
You will find this pattern is extremely efficient so you'll even want to delay it by restarting a timer in C++. Furthermore, it is also beautifully declarative and clean.
How can I get a reference to the AutoCompleteForm or
AutoCompleteListener's QML parent?
The only alternative to rootObject()->findChild() appears to be QQmlProperty::read. There is several of read() function overloads so you can specify the context more precise. You have to provide the name for the object you would like to fetch and make it a property of some root object to start with. I like the general article on this subject of interfacing between QML and C++.
To accomplish precisely what you want or access the parent of certain known property you can try QQmlContext::parentContext together with QQmlProperty::read that accepts the context and see if the empty object name allows to resolve the object then.
You can pass it as a QVariant:
Q_INVOKABLE void QmlLink::pass_object(QVariant v)
{
YourObject* tempObject = (YourObject*) v.value<void *>();
...
}
qml:
my_bound_property.pass_object(the_object);

Create custom C++ Object in QML and store it in a C++ Model

I have written a customer Class (inheriting from QObject) in C++ and registered it's type successfully with QML. Currently I'm creating objects of this class statically in C++ and storing a pointer to them in a Model which implements QAbstractListModel. In QML in I can access the objects perfectly as items of the Model.
The customObject is a non-visual object.
I'm visualising in another part of the GUI application (QML) the objects in a ListView with a delegate.
However now I would like to create objects from my custom Class dynamically in QML and store them also in the Model. This is where I'm struggling. I hoped I could create a customObject like this:
import com.myProject.myCustomStuff 1.0
...
Button{
id: createObjBtn
text: "create new CustomObj"
onClicked:{
var obj = MyCustomObj;
myObjectManager.addObj(obj); // object holding the implemented QAbstactListModel
console.log(typeof(obj)); // returns [Object object]
console.log(Qt.isQtObject(obj)) // returns false
}
}
I would appreciate your thoughts. Maybe someone knows a way to do this correctly?
Thanks!
Update 1
As requested by Simon-Warta, here is the Constructor implementation of MyCustomObj.
MyCustomObj.cpp
MyCustomObj::MyCustomObj(QObject *parent) : QObject(parent)
{
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
}
You are confusing the functionality intent of the classes. The QAbstractListModel is intended as a wrapper around a container, yes, you could put the container inside the QAbstractListModel derived class, but you don't really have to, the container can be just about any C++ class, not necessarily even QObject derived, it can be just a QVector<Something> that you can reach from the model via a pointer. Which is good for cases where you have many objects, and not all need to have models at all the time, since those models are pretty heavy.
You don't really need to concern yourself with the ownership, leave that at the C++ side, the same goes for the actual object creation, have a slot called that adds the new object to the container while also using the model's beginInsertRows() and endInsertRows() so that any views will be notified to update efficiently, the new object should also be created in that slot, you can pass any data needed for it from QML, just make sure all data is registered with the Qt meta system so it can work with QVariant for QML-C++ interop.
So it should be something like:
myObjectManager.create(/* any needed data goes here */)
And create() passes eventual data to the C++ side, where you create the object, call beginInsertRows(), add the object to the model's underlying storage, then call endInsertRows() and you are done.
I'd prefer to keep the ownership in the C++ side (and I don't mean explicitly), where I have control over it. Qt kind of sucks in a big way when dealing with object ownership shared between C++ and QML. Ideally, there should be a single shared pointer class that will work across both C++ and QML, so the object is deleted once all reference to it are gone. But that is just not the "Qt way" - the Qt shared pointers do not work with QML, nor do the standard C++ shared pointers, there is actually an entirely different shared reference class for QML, which is not even part of the public API IIRC, very ugly and shortsighted design that only widens the gap between C++ and QML and the associated inconvenience factor.
I don't know if this is the shortest way but this should do for you. I am starting with the basics for those other up-voters.
MyCustomObj.h
class MyCustomObj : public QObject
{
Q_OBJECT
public:
// ...
Q_INVOKABLE void funfunction();
MyCustomObj.cpp
void MyCustomObj::funfunction()
{
qDebug("Fun with QML");
}
main.cpp
qmlRegisterType<MyCustomObj>("com.myProject.myCustomStuff", 1, 0, "MyCustomObj");
app.qml
import com.myProject.myCustomStuff 1.0
ApplicationWindow {
id: mainWindow
Component {
id: myComponent
MyCustomObj {
}
}
Component.onCompleted: {
var obj = myComponent.createObject(mainWindow)
if (!obj) console.error("Error creating object")
console.log(typeof(obj))
console.log(Qt.isQtObject(obj))
obj.funfunction()
}
}
createObject optionally takes properties to be passed to the component.
Storing
Since you are responsible for deleting the objects now, I'd recommend to use shared pointers, such that the objects get destroyed when the List is destroyed.
Your implementation of QAbstactListModel, let's call it MyModel has an adder function like that:
#include <memory> // for std::shared_ptr
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
// ..
Q_INVOKABLE addObj(MyCustomObj* obj)
{
objectlist_.append(std::shared_ptr<MyCustomObj>(obj));
}
private:
QList<std::shared_ptr<MyCustomObj>> objectlist_
}

call QML function from C++ with another QML object as parameter

I have a QML file Dialog.qml which is ApplicationWindow and Tab.qml which is Item. There are TabView in Dialog.qml which tabs is formed from Tab.qml. I want to create Tab.qml in Qt, connect its signals to classes in Qt and add that tab to Dialog. Here is relevant part of code:
//Tab.qml
Item {
id: tb
anchors.fill: parent
//...here is signals and controls like RadioButtons and TextFields
}
//Dialog.qml
ApplicationWindow {
visible: true
title: "settings"
flags: Qt.Dialog
//...
function addTabfromCpp(tab){
frame.addTab("from c++", tab);
}
TabView {
id:frame
//...
}
}
I know how to call QML functions from Qt when function parameters are primitive types(via QVariant). But how to call function when its parameter is QQuickWindow or other and it is not possible to create QVariant from it?
You can pass QML object ids as function parameters just fine. In C++ those work as QObject *. Also, besides ids you can use the parent property, or children[indexOfChild]. Note that you don't pass the actual objects, since that would require invoking a copy constructor, and copying is disabled for all QObject derived classes, it is passed by reference, which works in JS with var and in C++ with QObject*. In a similar fashion, you can use QObject* to pass objects to QML. Additionally, you might want to add your custom types to the Qt metatypes with qRegisterMetaType() or Q_DECLARE_METATYPE, which will give you autocomplete for that type in QML.
In order to expose C++ functions in the QML context, you need to make those functions either slots or Q_INVOKABLE of a QObject derived class, instantiate the class and use setContextProperty() to expose it to QML.
Or alternatively, you can register that object to the QML engine so you can instantiate it, you can even share the same data across multiple instances of that object by following this example.
EDIT: OK, try this one:
QML:
ApplicationWindow {
objectName: "blah"
//...
function foo(obj) { console.log(obj) }
}
CPP:
QObject * root = engine.rootObjects().at(0);
QMetaObject::invokeMethod(root, "foo", Q_ARG(QVariant, QVariant::fromValue(root)));
and output is
qml: ApplicationWindow_QMLTYPE_11_QML_12(0x4121e70, "blah")
Seems you have to pass it as a Q_ARG(QVariant, QVariant::fromValue(QObject*)
However, TabView.addTab() expects not an object (which is an instance of a Component) but a component, i.e. a prototype for an object.
One way you can do that is:
QObject * root = engine.rootObjects().at(0);
QQmlComponent comp(&engine, QUrl("qrc:/Test.qml"));
QMetaObject::invokeMethod(root, "addTab", Q_ARG(QVariant, QVariant::fromValue(&comp)));

Qt Modeless Dialog Destruction

From what I understand to make dialog Modeless you have to allocate it on the heap. By Doing something like this:
MyDialog* dlg = new MyDialog(this);
dlg->show();
dlg->raise();
Since exec() ignores Modal Property. However now there is a memory leak since nothing deallocates memory pointed to by dlg pointer until the application is closed. I found one solution here http://tinf2.vub.ac.be/~dvermeir/manuals/KDE20Development-html/ch08lev1sec3.html#ch08list09 at the end of the page and was wondering whether there was a less cumbersome way having Modeless dialog.
You can use the attribute Qt::WA_DeleteOnClose to destroy the window when it is closed/hidden, and QWeakPointer (or QPointer) with a static variable to track the existence of the window inside the slot/function which opens it:
void MyWindow::openDialog() {
static QWeakPointer<MyDialog> dlg_;
if (!dlg_)
dlg_ = new MyDialog(this);
MyDialog *dlg = dlg_.data();
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->show();
dlg->raise();
dlg->activateWindow();
}
I'd schedule it for deletion at the time it's work is finished by using deleteLater:
void MyDialog::MyDialog(QWidget *parent) {
// ...
connect(this, SIGNAL(finished(int)), SLOT(deleteLater)));
}
This approach will preclude you from examining it after the finished signal has been emitted (unless you can guarantee that any accesses happen before everything gets back to the event loop when the deletion is actually performed).
Personally, I would choose between either using
dlg->setAttribute(Qt::WA_DeleteOnClose);
or making the dialog a -dynamically allocated- member i.e. creating it only once:
// constructor
: dialog_(0)
// member function
{
if (! dialog_)
dialog_ = new MyDialog(this);
dialog_->show();
dialog_->raise();
}
This way the dialog is deleted when the parent dies, and only needs to be constructed once.

Resources