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

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);

Related

How to run animation with blocked UI thread in Qt Widgets app?

In QML you can use Animator type to "animate on the scene graph's rendering thread even when the UI thread is blocked."
How can I achieve the same thing for Qt Widgets?
Basically, I want something like:
1) start loading screen / splash-screen
2) start GUI-blocking operation
3) stop loading screen / splash-screen
It is not possible to move the ui-blocking operation to a separate thread (Widgets are being created). I cannot modify this part.
I tried QQuickWidget and QQuickView containing the splash-screen scene with an Animator inside but it didn't work - they got blocked as well.
EDIT: In the separate thread I read the file containing the UI description. Then I recursively create hundreds of Widgets (including QQuickWidgets, QLabels for images, Web views etc.).
Point of the question was to see if there is a "workaround" for that (e.g. displaying the aforementioned QML scene in some separate window with an own event loop). Unfortunately at this point not much more can be done about the overall design of the above.
Probably the widgets you're creating do too much work. You have to specify exactly how many widgets you're creating, and how. Show some example code. In general, the GUI thread is for cooperative multitasking - if you have something that "blocks", break it down into tiny chunks. For example, suppose that you're processing some XML or json file to build the UI. You could have that task do it one widget at a time, and be invoked each time the event loop is about to block (i.e. use a zero-duration "timer" and invert control).
You should also do the maximum possible amount of work outside of the gui thread. I.e. the UI description should be read and converted to an efficient representation that encapsulates the work to be done in the main thread. This conversion has to be done asynchronously.
The simplest way to accomplish that is to encapsulate each widget's creation in a lambda that refers to some context object. Such a lambda would have the signature [...](BatchContext &ctx). The vector of those lambdas would be kept by the CreationContext object as well:
class BatchContext : public QObject {
Q_OBJECT
public:
using Op = std::function<void(CreationContext &)>;
using QObject::QObject;
// useful for Op to keep track of where things go
void push(QWidget *w) { m_stack.push_back(w); }
QWidget *pop() { return m_stack.isEmpty() ? nullptr : m_stack.takeLast(); }
QWidget *top() const { return m_stack.isEmpty() ? nullptr : m_stack.last(); }
int stackSize() const { return m_stack.size(); }
bool stackEmpty() const { return m_stack.isEmpty(); }
Q_SLOT void startExec() {
if (m_execIndex < ops.size())
m_execTimer.start(0, this);
}
template <typename F>
void addOp(F &&op) { m_ops.push_back(std::forward<F>(op)); }
...
private:
QVector<Op> m_ops;
QVector<QWidget *> m_stack;
QBasicTimer m_execTimer;
int m_execIndex = 0;
void timerEvent(QTimerEvent *ev) override {
if (ev->timerId() == m_execTimer.timerId())
if (!exec())
m_execTimer.stop();
}
/// Does a unit of work, returns true if more work is available
bool exec() {
if (m_execIndex < m_ops.size())
m_ops.at(m_execIndex++)(*this);
return m_execIndex < ops.size();
}
};
After the context is created asynchronously, it can be passed to the main thread where you can then invoke startExec() and the widgets will be created one-at-a-time. The stack is but an example of how you might implement one aspect of widget creation process - the tracking of what widget is the "current parent".

How to report errors from custom QML components?

I've made a MyComponent QML component with some property:
Q_PROPERTY(PendingList* list MEMBER list)
and a function:
Q_INVOKABLE void send();
It can be created in QML:
MyComponent {
id: myComponent
}
When I call somewhere the myComponent.send() while the list property is not defined, how can I properly report the problem in the stderr? I'd like to see the *.qml file name and line number where send() was called or the line number of the myComponent creation.
Is there a proper way to maybe get the QML stack trace or generate QQmlError or throw an exception that will be handled by the QML engine?
Will have to use some private stuff.
QTDD14 - Categorized logging in QML - Giuseppe D'Angelo
Git repository with the slides and the code: qmllogging
Short Version
Add to CMakeLists.txt:
include_directories(${Qt5Quick_PRIVATE_INCLUDE_DIRS})
Add some QV8Engine stuff to your Q_INVOKABLE function or slot:
#include <QtQml>
#include <private/qv4engine_p.h>
#include <private/qv8engine_p.h>
void Logger::log(const QString &message)
{
const QV4::StackFrame frame = QV8Engine::getV4(m_engine)->currentStackFrame();
QMessageLogger(qPrintable(frame.source),
frame.line,
qPrintable(frame.function)).warning("%s", qPrintable(message));
}
Geting the engine for that function:
QQuickView view;
m_engine = view.engine();
Also set the message pattern to actually show the line number (somewhere in the beginning of main.cpp is fine):
qSetMessagePattern("%{file}:%{line} - %{message}");

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_
}

Qt/QML: Display an indicator depending on if something is downloaded or not

I'm wondering what might be the best approach to the following situation:
I have a QML file that is load from a HTTP server to a Qt/QML android app to display the UI.
The user can tap on thumbnails of catalogs and make the app download another QML file for each catalog. The catalog QML is downloaded and stored on the device for offline use.
This means I have a number of directories that have a unique ID to store the catalog QML and assets. Something like this:
/my/app_data_path/catalogs/CATALOG_001
/my/app_data_path/catalogs/CATALOG_007
/my/app_data_path/catalogs/CATALOG_010
In the UI I'd like to show an indicator that tells if a catalog has been downloaded already to the device. What would be the best approach within QML to show/hide an indicator depending on that?
Rectangle {
id: indicator
visible: MyApp.catalogIsLoaded('some ID here')
}
This is something that came to my mind, but I don't think it's the best way to do this since I'd need a method to pass the catalog ID in order to check if the data directory exists. Also I'd have to figure out a way to re-evaluate the visible state/call the catalogIsLoaded method from time to time - especially after downloading or deleting catalogs.
Is there a better/cleaner approach to this?
A better way would be to associate your indicator visibility with a property binding.
As you wish to find a catalog by its id, provide an invokable method on your 'MyApp' class to return a catalog reference.
Q_INVOKABLE Catalog* findCatalogById(const QString& id);
Catalog will be a QObject, expose a isLoaded property to QML.
class Catalog : public QObject {
Q_OBJECT
Q_PROPERTY(bool isLoaded READ isLoaded NOTIFY isLoadedChanged)
public:
Catalog(QObject* parent = 0) : QObject(parent) {
}
bool isLoaded() const { return m_isLoaded; }
void setIsLoaded(bool loaded) {
if (m_isLoaded != loaded) {
m_isLoaded = loaded;
emit isLoadedChanged();
}
}
signals:
void isLoadedChanged();
private:
bool m_isLoaded; //should probably be initialized to false
};
Of course, the catalog reference provided by your context should be memorize somewhere (like in a QHash<QString, Catalog*>), and be updated accordingly when its status changed.

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)));

Resources