How to make a Qt QML variable accessible anywhere - qt

I would like to have a qt QML var accessible globally, and anywhere else in my qml files. Is this possible?
I know that upon creating a variable in a C++ object can be accessed in QML by exposing its getter function, but this only works if you know the type of the data type e.g. string, int, bool.
Is there a variable data type (or class) that can handle a QML var in C++, so that I can only call it in the other parts of the QML files?

AS Amfasis said, you can use the rootContext, so you can access it from anywhere in QML - as long as you do not shadow the name. Alternatively you can also register a Singleton to QML.
For both, you first need to create a QObject
public class MyContextObject: public QObject {
Q_OBJECT
Q_PROPERTY(QVariant myVar READ myVar NOTIFY myVarChanged)
QVariant m_myVar;
public:
MyContextObject(QObject* parent = nullptr) : QObject(parent) {}
QVariant myVar() { return m_myVar; }
void setMyVar(QVariant var) {
if (var == m_myVar) return;
m_myVar = var;
emit myVarChanged();
}
signals:
void myVarChanged();
}
This object you create in your main and set it as a contextProperty
MyContextObject* mctx = new MyContextObject();
view.rootContext()->setContextProperty("myCtx", mctx);
To set it from C++ use the setter. On the QML-side just bind to myCtx.myVar
Expose the setter also, if you want to modify it from QML also
This is not tested, I don't have a Qt development environment available right now.
To expose it as singleton, you can use this function:
https://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterSingletonType-1

Related

Understanding QML_INTERFACE

I've a really hard time understanding what the QML_INTERFACE and QML_IMPLEMENTS_INTERFACES macros are supposed to do. Naively I assumed that the macro does what it says it does, registering a non-instantiable ("uncreatable") type to the QML type system. A type I can then use as a custom property to bind to a specific implementation.
Let's get concrete (no pun intended) and elaborate with a simple example, an interface with a single QString property:
class QmlInterface : public QObject {
Q_OBJECT
QML_INTERFACE
Q_PROPERTY(QString name READ get WRITE set NOTIFY changed)
public:
virtual ~QmlInterface();
virtual QString get() const = 0;
virtual void set(QString const& name) = 0;
signals:
void changed();
};
Q_DECLARE_INTERFACE(QmlInterface, "QmlInterface")
And a class which implements that interface (declarations omitted for simplicity):
class QmlImplA : public QmlInterface {
Q_OBJECT
QML_ELEMENT
QML_IMPLEMENTS_INTERFACES(QmlInterface)
public:
QString get() const final;
void set(QString const& name) final;
private:
QString name_;
};
An instance of this implementation is then created in C+ and an interface pointer gets passed to the QML engine right before loading the .qml file
QQmlApplicationEngine engine;
// url to .qml file
std::unique_ptr<QmlInterface> qml_impl_a{std::make_unique<QmlImplA>()};
engine.setInitialProperties({{"qml_impl_a", QVariant::fromValue(qml_impl_a.get())}});
engine.load(// url);
I then assumed I could just import the interface in my .qml file and use it with the underlying implementation like this:
import QtQuick
import QtQuick.Controls
import QmlInterface
ApplicationWindow {
required property QmlInterface qml_impl_a
// ...
}
However this doesn't work. QML complains that QmlInterface is not a type
Upon googling for some QML_INTERFACE examples I found a passage in Cross-Platform Development with Qt 6 and Modern C++ which says that:
QML_INTERFACE registers an existing Qt interface type. The type is not instantiable from QML, and you cannot declare QML properties with it.
Wait... what? Is this true? The official documentation doesn't mention this anywhere. As this would practically render interfaces completely useless I assume the book is wrong here?
For now I got my example working by making the interface a normal QML_ELEMENT and adding QML_UNCREATABLE to prevent instantiation from inside QML. This works and does what I intended in the first place. I still wonder though did I do something wrong or are the macros supposed to do something else entirely and are just named badly?

Q_GADGET has value type semantic?

From the docs (https://doc.qt.io/qt-5/qtqml-cppintegration-data.html#value-types) and other posts I got the impression, that Q_GADGET can be used as a value type with copy semantics.
Let's take the Actor from the docs:
class Actor
{
Q_GADGET
Q_PROPERTY(QString name READ name WRITE setName)
public:
QString name() const { return m_name; }
void setName(const QString &name) { m_name = name; }
private:
QString m_name;
};
Q_DECLARE_METATYPE(Actor)
Now I expose an Actor with Q_PROPERTY on some C++ object like this:
Q_PROPERTY(Actor actor READ actor WRITE setActor NOTIFY actorChanged)
So far, everything is fine.
Now I have some javascript code in QML (context is the C++ object on which the Q_PROPERTY actor is exposed:
{
var actorCopy = context.actor
actorCopy.name = "Tom"
}
I always assumed that the Actor-Gadget will be copied, when assigning to the javascript variable actorCopy and I change the name on a copy.
Now I set a breakpoint in the setActor function of the defined Q_PROPERTY.
What happens is, when assigning the name of the actor in javascript, the setActor method will be called.
I would not expect that, since I work on a copy.
My question is, do I have a wrong understanding of Q_GADGET or is this a bug?
I mean, if I would have a O_OBJECT instead of Q_GADGET I would not expect the setActor function being called either, since I don't assign the property.
So Q_GADGET is neither a lightweight Q_OBJECT nor a value type.
Try something like this:
{
var actorCopy = context.actor
var otherCopy = actorCopy
actorCopy.name = "Tom"
otherCopy.name = "Gatis"
}
Then in your debugger you will see that setName() is called on two different instances. You can print "this" pointer to confirm.
I did not run this code, but I think that is how it should work.

Using ENUMS from different classes (or namespaces) for slots called from QML

I have a class (e.g. MyEnumClass, Q_GADGET) in which I define an enum, e.g. MyEnum.
I call Q_ENUM(MyEnum) to register it to the metaobject, and register the whole class as uncreatable type to QML.
In my second class (MyObject : QObject with macro Q_OBJECT) I have a slot that consumes a MyEnum as parameter. This object is registered as regular type to QML (creatable).
I want to call the slot from QML with a value from MyEnum - this fails, as the Type MyEnumClass::MyEnum seems to be unknown.
When the enum is defined inside the class with the slot, it works fine.
MVCE
class MyEnumClass {
Q_GADGET
public:
enum MyEnum {
E1,
E2,
E3
};
Q_ENUM(MyEnum)
};
class MyObject : public QObject
{
Q_OBJECT
public:
MyObject(QObject* parent = nullptr) : QObject(parent) {}
enum TestEnum {
V1,
V2,
V3
};
Q_ENUM(TestEnum)
public slots:
void testFun1(MyEnumClass::MyEnum val) { qDebug() << val; }
void testFun2(TestEnum val) { qDebug() << val; }
};
in main.cpp:
qmlRegisterUncreatableType<MyEnumClass>("MyObject", 1, 0, "MyEnum", "Uncreatable");
qmlRegisterType<MyObject>("MyObject", 1, 0, "MyObject");
in main.qml:
import MyObject 1.0
ApplicationWindow {
id: window
visible: true
width: 600
height: 600
MyObject {
id: obj
}
MouseArea {
anchors.fill: parent
onClicked: {
console.log(MyObject.V2)
console.log(MyEnum.E2)
obj.testFun2(MyObject.V2)
obj.testFun1(MyEnum.E1)
}
}
}
I tried to inherit MyEnumClass in MyObject to make the enum part of MyObject, I tried with different macros and functions to make the enum even more available in the MetaObjectSystem... to no avail.
I also tried to put an enum in a namespace as described here - it was also unusable for a slot.
The only way I found to have the slot called, was by removing the enum and using int as type for the parameter - which is not that nice...
How can I make this work?
Are there any tricks I am missing?
Register metatype:
qRegisterMetaType<MyEnumClass::MyEnum>();
Explanation:
From Q_ENUM( ...) documentation:
This macro registers an enum type with the meta-object system. It must
be placed after the enum declaration in a class that has the Q_OBJECT
or the Q_GADGET macro. For namespaces use Q_ENUM_NS() instead.
...
Registered enumerations are automatically registered also to the Qt
meta type system, making them known to QMetaType without the need to
use Q_DECLARE_METATYPE().
Using Q_ENUM automatically registers enum with meta-object system so you don't need to add
Q_DECLARE_METATYPE(MyEnum)
But to use enum in queued signal slot connections, properties... you need to register meta type.
As said in int qRegisterMetaType() documentation:
To use the type T in QVariant, using Q_DECLARE_METATYPE() is
sufficient. To use the type T in queued signal and slot connections,
qRegisterMetaType() must be called before the first connection is
established.
Also, to use type T with the QObject::property() API,
qRegisterMetaType() must be called before it is used, typically in
the constructor of the class that uses T, or in the main() function.
Extending #Eligijus Pupeikis' answer, In my case I got the following error:
Error: Unknown method parameter type: Parameter::Id
And solved it by registering the exact type name it was looking for:
qRegisterMetaType<Parameter::Id>("Parameter::Id");
where Parameter::Id is an enum class inside the Parameter class.

How to bind a property to a singleton object property from QML

There is a question about how to bind from a singleton object property to a QML property. But what about if we like to bind a QML property to a singleton object.
Here is the singleton class definition,
class Singleton : public QObject {
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName)
public:
explicit Singleton(QObject *parent = nullptr);
QString name() const;
void setName(const QString &name);
private:
QString m_name;
};
And on QML
property string qmlName: textField.text
TextField {
id: textField
}
I would like to bind textField.text to Singleton object name property. It is possible to bind it with a workaround like,
onQmlNameChanged: {
Singleton.name = qmlName;
}
But that won't be an Property Binding actually, because it is an assignment.
So is there a more nature binding way to a singleton object property?
You could try to assign the binding like this:
Component.onCompleted: Singleton.name = Qt.binding(function() { return qmlName })
It works for normal QML-Objects, not sure it works with a singleton class, though. Anyhow, you can read more about this approach in the section "Creating Property Bindings from JavaScript".
That is essentially what a property binding does, at least I assume it is what it does - it connects the changed() signals of the related variables to reevaluating the bound expression which references them.
So this in practice is a binding:
onQmlNameChanged: {
Singleton.name = qmlName;
}
You will only have a problem if you only execute the assignment once, but if it is attached to a signal it will keep updating as expected form a binding.
That would be identical to a Singleton.name : qmlName, unfortunately, the QML syntax does not allow to do it in this form. So for all intents and purposes, you do have a binding, even if it uses a different syntax to achieve it.
In fact this shouldn't be much different from what QML does under the carpet for you. For example the binding:
someProp : anotherProp + yetAnotherProp
is implement as something like this:
function unnamed(this) { return this.anotherProp + this.yetAnotherProp }
anotherPropChanged.connect(function(this) { this.someProp = unnamed(this) })
yetAnotherPropChanged.connect(function(this) { this.someProp = unnamed(this) })
Obviously, that is quite cumbersome to do manually especially as the expression becomes more complex and references more objects, so QML is doing it for you.

Null pattern with QObject

(C++/Qt) I have a smart pointer to a QObject. Let's say a QWeakPointer. For some external reason (something that might happen in another object or due to an event), it is possible that the pointed object gets destroyed. Since I have a smart pointer there will be no dangling reference, so there's no problem. But I always have to check if the pointer is null or not.
I'm thinking of using the null pattern in order to avoid checking this all the time but I'm not sure if this is possible or convenient with a QObject. The idea would be that the pointer points to the object and in case it gets destroyed, the smart pointer changes its pointed object to a null object. Is this a good idea or should I forget it and just check if the pointer is NULL all the time?
Let's show you an example. We have a worker who uses a tool to do its work:
class Worker : public QObject
{
Q_OBJECT
public:
Worker(QObject *parent = 0);
void work()
{
if(m_tool)
m_tool->use();
emit workCompleted();
};
signals:
workCompleted();
public slots:
void setTool(QWeakPointer<Tool> tool);
private:
QWeakPointer<Tool> m_tool;
};
class Tool : public QObject
{
Q_OBJECT
public:
Tool();
public slots:
void use() =0;
};
class Screwdriver : public Tool
{
Q_OBJECT
public:
Screwdriver() : Tool();
public slots:
void use()
{
// do something
};
};
class Hammer : public Tool;
class Saw : public Tool;
...
In this case, the Tool is a public domain object of a library, which is used by the Worker. I'm developing such library. So the worker is using a screwdriver but it gets broken and gets destroyed. No problem:
if(m_tool)
m_tool->use();
emit workCompleted();
m_tool is 0 so it simply does nothing. But we have to check that it's not null everytime.
Now let's say we had a NullTool object:
class NullTool : public Tool
{
Q_OBJECT
public:
NullTool() : Tool();
public slots:
void use()
{
// does nothing
};
};
When the tool was destroyed, our pointer would be smart and would know it should point to a NullTool instance. So Worker::work() could be implemented like this:
void Worker::work()
{
m_tool->use();
emit workCompleted();
};
m_tool->use() would then get called on the NullTool which does nothing, so there would be no need to check the pointer is not null.
Is this a good idea? Is it possible with the smart pointer classes Qt provides or should I subclass QWeakPointer?
I think the null object pattern makes most sense for value-like classes. Examples are QString or QVariant, were you don't want to have code like if ( str && !str->isEmpty() ) but just do if ( !str.isEmpty() ). For QObjects, which are not values but have "an identity", I never found this useful.
I don't understand clearly your use case, but your program can be signaled when the object has been destroy by connecting the following signal from QObject:
void destroyed ( QObject * obj = 0 );
I don't see any problem in your idea. You just have to compare the work that it takes to implement it compared to the work for checking the pointer every time. Let's your checking the pointer 10.000 times it's a good idea to use your approach. Side note: Your null object pattern rely on the fact that Tool::use() has no side effects whatsoever.
Take care that possible side affects in Tool::use() don't get in the way when you replace it polymorphically with NullTool::use(). In other words: Be sure you don't break the Liskov Substitution Principle.

Resources