How to connect a JavaScript function to a property signal? - qt

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

Related

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.

QML Connections handle event from service results in segfault

I am having a hard time getting to understand the non-standard C++ world that is qt. I have a class that can emit a 'login' signal, that I want to listen to from QML, this simply segfaults.
class Service : public QObject
Q_OBJECT
{
public:
Service()
{
// get the context, snipped for brevity
rootContext->setContextProperty("service", this);
}
public signals:
void login(bool succcess);
public slots:
void method();
};
I can successfully call 'service.method' from the QML, but if I add a Connections section to listen for the login event, I get a segfault whenever that component is displayed.
Page {
Component {
Column {
...
Connections {
target: service
onLogin: {
console.login("TEST");
}
}
}
}
}
I have tried moving the 'Connections' section out of the columns, but this results in a runtime-error and a white page, because it fails to parse the QML. What am I missing here?
I am using QT Creator 4.2.1 if that is in any way relevant.
Found the problem. Apparantly you cannot have a slot and a signal by the same name - even if they have wildly different function signatures. It doesn't give a compile error, it simply crashes.
After changing the name of the signal it magically started working.

On emitting signal, control comes back to the same class in qt

In my application I am deleting a label and in method I emit a signal after deleting label and in other class I used a slot to connect to that signal. After that in that slot I don't want to return to previous class. how can I do that?
As i know, the default connection of Qt's signal is autoconnection, which actually is directconnection in single Thread.
I guess you are in single Thread app.
When a signal is emitting, it is actually calling slot method, just like general function call.
For a example:
void test()
{
return 0;
}
void main()
{
test();
}
So it will return to your calling method.
There are some standard way to use Qt. You don't need to play tricks.

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.

QML On Item Changed Signal

MyObject
Item {
property int current: 0
}
Can this be configured to emit a signal such that the following works?
Item {
property variant myObj: MyObject {}
onMyObjChanged: doThis()
...
}
cmannet85 has answered your question: it's not possible. Perhaps you could post more code so we can suggest alternative approaches.
In terms of a solution using the information you've provided, you should expose signals that client code should connect to in order to know when the object has changed. Since you said the current property is all that matters, and it already has a change signal, you can use Connections:
Connections {
target: myObj
onCurrentChanged: doThis()
}
Or connect to the signal manually:
Component.onCompleted: {
myObj.onCurrentChanged.connect(doThis);
}
function doThis() {
// ...
}

Resources