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

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.

Related

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.

How to make a Qt QML variable accessible anywhere

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

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.

Qt - Saving a custom QGraphicsScene with custom QGraphicsItems

I created a public QGraphicsItem Node class which has getter/setter methods for a bunch of properties relevant to my application. The application is a diagram editor in which the users design models. This Node class is used to populate an also public QGraphicsScene Diagram class.
I'm now trying to implement a load/save mechanism in the app that enables users to save and reuse the models - editing them as they were when they saved. I'm having some trouble figuring out how to attack this problem.
I already know I have to get the relevant properties for the QGraphicsItem objects and save those to file, and then on load, reconstruct the scene using the data I saved. My question is: when I use the QGraphicsScene::items() function, it returns a QList<QGraphicsItem *>. What can I do to have it return a QList<Node *>?
If I get a list of all the nodes I appended to the scene, I know what to do next.
I began to realise that I will probably have to reimplement the items() function in my Diagram class, but I was hoping I could get away with it more easily. In this case, can someone please explain how to do it?
I would suggest to implement the QGraphicsItem::type() method and using qgraphicsitem_cast to cast into the desired class. In your case, you could subclass all your custom GraphicsItems from a common base class.
Your base class would look like this:
class MyGraphicsItem: public QGraphicsItem
{
enum { Type = UserType + 1 };
int type() const { return Type; }
};
Your Node and your Link class would inherit this base:
class Node : public MyGraphicsItem
{
// ...
};
class Link : public MyGraphicsItem
{
// ...
};
Somewhere else you could cast a QGraphicsItem into your base class MyGraphicsItem like this for example:
QList<QGraphicsItem*> allItems = graphicsScene->items();
foreach (QGraphicsItem *item, allItems) {
// Using qgraphicsitem_cast
MyGraphicsItem* graphicsItem = qgraphicsitem_cast<MyGraphicsItem*>(item);
if (graphicsItem) {
// Do something with MyGraphicsItem
}
}
While the items function returns a list of QGraphicsItem pointers, you could try either dynamic_casting to check if it's a Node pointer or use the Qt metadata and the type() function.
However, the method I often use, which can help you in other ways too, is to maintain a unique id for every type of object and a static lists of item Ids. For example: -
class BaseItem : public QGraphicsItem
{
public:
BaseItem(QGraphicsItem* parent)
: QGraphicsItem(parent), m_Id(m_NextId++) // initialising the object's unique Id
{
}
unsigned int ID() const
{
return m_Id;
}
private:
static unsigned int m_NextId; // the next Object Id, initialised to 0 in implementation
unsigned int m_Id; // the object's unique Id
}
class Node : public BaseItem
{
public:
Node(QGrahicsItem* parent)
: BaseItem(parent)
{
m_NodesList.push_back(m_Id);
}
private:
static QList<unsigned int> m_sNodesList; // all Node ids in the scene
QList<unsigned int> m_linkedNodeList; // linked nodes
}
All items added to the GraphicsScene are inherited from the BaseItem
You also have a static list of Node Ids, so you can iterate through all of them for loading / saving and you can add a static helper function in the base class to return a list of Nodes, by searching through the List of Node Ids and matching them to their node pointers in the scene.
This architecture also allows you to include a list of linked node ids in each Node object, rather than using the parent / child system of Qt, which in the case of a node diagram, isn't always what is needed.
Overall, I've used this architecture for many QGraphicsScene applications and it has really made development very easy for items that have complex links to other items.

How to connect a JavaScript function to a property signal?

I'm new to Qml and having some trouble connecting a javascript handler to a property's signal. I have a C++ object with a property and signal.
class CppObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariant value READ getValue WRITE setValue NOTIFY valueChanged)
signals:
void valueChanged(const QVariant &);
};
The objects are created through a C++ factory method and I'm able to bind the values
and changes to Qml properties. This all works.
property CppObject obj: cppProxy.PropertyFactory("foo");
Text
{
x: 100;
y: 100;
text: parent.obj.value;
}
For some properties, I'd like to connect the valueChanged signal to a javascript function.
I've been up and down through the Qml documentation and have tried a bunch of stuff without
any luck. I figured something like this should work, but doesn't
function objEventHandler()
{
console.log('objEventHandler() ran')
}
Component.onCompleted:
{
obj.value.valueChanged.connect(objEventHandler);
}
What is the best way to do this?
You can also connect as you've tried in your example, but the form is:
Component.onCompleted:
{
obj.valueChanged.connect(objEventHandler);
}
The signal is not a property of the 'value' object, but of 'obj'.
It's simple using the connections object.
Connections
{
target: obj;
onValueChanged: console.log('changed');
}

Resources