PDA

View Full Version : Catching Qt/C++ signal from QML custom Item/code - howto



MarkoSan
17th September 2015, 07:52
I am working on QML app with Qt/C++ "back" logic. Now, I've created class named UeStatus, which resemble the state of app (which users are logged in, database connection succesfull or not, ...):


#ifndef UESTATUS_H
#define UESTATUS_H

#include <QObject>
#include <QList>

#include "../core/uetypes.h"
#include "../core/uedatabaseconnectionstatus.h"

class UeStatus : public QObject
{
Q_OBJECT

Q_PROPERTY(UeTypeLoggedUsers* m_ueLoginCandidates
READ ueLoginCandidates
WRITE ueSetLoginCandidates
NOTIFY ueSignalLoginCandidatesChanged)
Q_PROPERTY(UeTypeLoggedUsers* m_ueLoggedUsers
READ ueLoggedUsers
WRITE ueSetLoggedUsers
NOTIFY ueSignalLoggedUsersChanged)
Q_PROPERTY(UeDatabaseConnectionStatus::UeTypeDatab aseConnectionStatus m_ueDatabaseConnectionStatus
READ ueDbConnectionStatus
WRITE ueSetDbConnectionStatus
NOTIFY ueSignalDatabaseConnectionChanged)

private:
UeTypeLoggedUsers* m_ueLoginCandidates;
UeTypeLoggedUsers* m_ueLoggedUsers;
UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus m_ueDatabaseConnectionStatus;

public:
explicit UeStatus(QObject *parent = 0);
~UeStatus();

inline UeTypeLoggedUsers* ueLoginCandidates() const
{ return this->m_ueLoginCandidates; }
inline UeTypeLoggedUsers* ueLoggedUsers() const
{ return this->m_ueLoggedUsers; }
inline UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus ueDbConnectionStatus() const
{ return this->m_ueDatabaseConnectionStatus; }

inline void ueSetLoginCandidates(UeTypeLoggedUsers* const m_ueLoginCandidates)
{ this->m_ueLoginCandidates=m_ueLoginCandidates; }
inline void ueSetLoggedUsers(UeTypeLoggedUsers* const loggedUsers)
{ this->m_ueLoggedUsers=loggedUsers; }
inline void ueSetDbConnectionStatus(const UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus& status)
{ this->m_ueDatabaseConnectionStatus=status; }

signals:
void ueSignalLoginCandidatesChanged();
void ueSignalLoggedUsersChanged();
void ueSignalDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus& newStatus);

public slots:
void ueSlotDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus& newStatus);
};

#endif // UESTATUS_H

As you can see, I've also created dedicated class/enum for describing database connection state:


#ifndef UEDATABASECONNECTIONSTATUS
#define UEDATABASECONNECTIONSTATUS

#include <QObject>

class UeDatabaseConnectionStatus : public QObject
{
Q_OBJECT

public:
enum UeTypeDatabaseConnectionStatus
{
NOT_CONNECTED=false,
CONNECTED=true
};

Q_ENUM(UeTypeDatabaseConnectionStatus)
};

#endif // UEDATABASECONNECTIONSTATUS

Now, I register class UeDatabaseConnectionStatus inside main.cpp:


#include <QtQml>
#include <QApplication>
#include <QQmlApplicationEngine>

#include "database/uepeoplemodel.h"
#include "core/uestatus.h"
#include "core/uedatabaseconnectionstatus.h"

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;

UeStatus* ueApplicationStatus=new UeStatus(qApp);
UePeopleModel* uePeopleModel=new UePeopleModel(qApp);

QObject::connect(uePeopleModel,
SIGNAL(ueSignalDatabaseConnectionChanged(UeDatabas eConnectionStatus::UeTypeDatabaseConnectionStatus) ),
ueApplicationStatus,
SLOT(ueSlotDatabaseConnectionChanged(UeDatabaseCon nectionStatus::UeTypeDatabaseConnectionStatus)));

uePeopleModel->ueConnectToDatabase();

engine.rootContext()->setContextProperty("uePeopleModel",
uePeopleModel);
engine.rootContext()->setContextProperty("ueApplicationStatus",
ueApplicationStatus);
engine.addImageProvider(QLatin1String("uePeopleModel"),
uePeopleModel);

qmlRegisterUncreatableType<UeDatabaseConnectionStatus>("si.test",
1,
0,
"UeTypeDatabaseConnectionStatus",
"Database Connection Status");

engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

return app.exec();
}

and the GUI (QML) is defined in main.qml:


import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.0
import QtQuick.Controls.Styles 1.4

import "gui/windows"
import "gui/items"

import si.mikroelektronika 1.0

ApplicationWindow
{
id: ueWindowMain

signal ueSignalDatabaseConnectionChanged()

title: qsTr("uBlagajna Mobile Client ver 1.00")

width: Screen.desktopAvailableWidth
height: Screen.desktopAvailableWidth

visible: true

opacity: 1.0

contentOrientation: Qt.LandscapeOrientation

color: "black"

UeKeypad
{
id: ueLoginKeypad
} // ueLoginKeypad

statusBar:
StatusBar
{
id: ueStatusBar

height: 96

clip: true

antialiasing: true


style:
StatusBarStyle
{
background:
Rectangle
{
color: "black"
} // Rectangle

panel:
Rectangle
{
color: "grey"
}
} // StatusBarStyle

RowLayout
{
spacing: 8

UeStatusIndicator
{
id: ueStatusIndicatorDatabaseConnected

ueParamImageStatusOn: "qrc:///ueIcons/icons/ueDbConnectionOk.png"
ueParamImageStatusOff: "qrc:///ueIcons/icons/ueDbConnectionError.png"
} // ueStatusIndicatorDatabaseConnected
} // RowLayout
} // ueStatusBar
} // ueWindowMain

UeStatusIndicator.qml is item that containts two images according to its state:


import QtQuick 2.0

Item
{
id: ueStatusIndicator

property string ueParamImageStatusOn
property string ueParamImageStatusOff

state: "ueStatusIndicatorDabaseNotConnected"

Image
{
id: ueStatusIndicatorCurrentImage

smooth: true

fillMode: Image.PreserveAspectFit

width: 96
height: 96

sourceSize.width: 96
sourceSize.height: 96
} // Image

Connections
{
} // Connections

states:
[
State
{
name: "ueStatusIndicatorDabaseConnected"

PropertyChanges
{
target: ueStatusIndicatorCurrentImage
source: ueParamImageStatusOn
} // PropertyChanges
}, // State

State
{
name: "ueStatusIndicatorDabaseNotConnected"

PropertyChanges
{
target: ueStatusIndicatorCurrentImage
source: ueParamImageStatusOff
} // PropertyChanges
} // State
] // states
} // Item

Now, how do I catch signal void ueSignalDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus& newStatus); or slot void ueSlotDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus& newStatus); inside ueStatusIndicator?
Sincerely,
Marko

anda_skoa
17th September 2015, 09:17
Now, how do I catch signal void ueSignalDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus& newStatus); or slot void ueSlotDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus& newStatus); inside ueStatusIndicator?

Why would you want to catch the signal?
The connection status is a property on one of the exported objects, you can simply bind to its value.

Cheers,
_

MarkoSan
17th September 2015, 09:27
Why would you want to catch the signal?
The connection status is a property on one of the exported objects, you can simply bind to its value.

Cheers,
_
I would like to change database connection in QML StatusBar from one representing connection error to one representing connection OK if connection gets screwed and so I can reconnect inside connection dropped slot.

anda_skoa
17th September 2015, 09:47
I am not sure I understand.

My assumption is that you want to change the value of "state" when the connection status changes.
But since "state" is a property it can be bound to an expression containing other properties (property binding), in this case the connection status property of the ueApplicationStatus object.

What JavaScript code do you want to execute on connection status change?

Cheers,
_

MarkoSan
17th September 2015, 09:58
I am not sure I understand.

My assumption is that you want to change the value of "state" when the connection status changes.
But since "state" is a property it can be bound to an expression containing other properties (property binding), in this case the connection status property of the ueApplicationStatus object.

What JavaScript code do you want to execute on connection status change?

Cheers,
_
Ok, let's go from start: When the user starts app, the app is in NOT_CONNECTED state and therefore in QML's StatusBar there is icon, which represents NOT_CONNECTED state. Then, the app tries to connect to mysql server and when it does connect, the responsible object emits signal to listeners notifiyng them database connection succesfully opened - fetching data available. At this point, on QML side I need to change database connection icon from icon representing NOT_CONNECTED state to icon representing CONNECTED state. Do you understand now?

Sincerely,
Marko

anda_skoa
17th September 2015, 11:23
Yes, I already understood that :)

Lets have a look at the code you posted:

- You have a class named UeStatus
- It has a property called m_ueDatabaseConnectionStatus (which, btw, is a very uncommon name for a property, m_ is usually used to designate member variables)
- You export an object of that class as ueApplicationStatus

Now, my assumption is that m_ueDatabaseConnectionStatus is updated then the connection status changes.
Is that correct?

If so, you can simply make "state" depend on the value of m_ueDatabaseConnectionStatus.
You could even make the image source depend on that in remove the states, but maybe you want to do more settings in each state than just the image source.

Cheers,
_

MarkoSan
17th September 2015, 12:00
Yes, I already understood that :)

Lets have a look at the code you posted:

- You have a class named UeStatus
- It has a property called m_ueDatabaseConnectionStatus (which, btw, is a very uncommon name for a property, m_ is usually used to designate member variables)
- You export an object of that class as ueApplicationStatus

Now, my assumption is that m_ueDatabaseConnectionStatus is updated then the connection status changes.
Is that correct?

If so, you can simply make "state" depend on the value of m_ueDatabaseConnectionStatus.
You could even make the image source depend on that in remove the states, but maybe you want to do more settings in each state than just the image source.

Cheers,
_
Ok, but how do I access m_ueDatabaseConnectionStatus from QML?

anda_skoa
17th September 2015, 12:19
Consider the first argument to setContextProperty() as equivalent to a QML "id" of the object.
I think you've already done that with the model.

Cheers,
_

MarkoSan
17th September 2015, 13:50
Consider the first argument to setContextProperty() as equivalent to a QML "id" of the object.
I think you've already done that with the model.

Cheers,
_
Ok, so there is no way to handle this via signal/slot, since this is what am I trying to do (this code does NOT work) - file UeStatusIndicator.qml, which is called from main.qml:


import QtQuick 2.0

import si.mikroelektronika 1.0

Item
{
id: ueStatusIndicator

property string ueParamImageStatusOn
property string ueParamImageStatusOff

signal ueSignalDatabaseConnectionChanged(UeTypeDatabaseCo nnectionStatus status) // from UeStatus

state: "ueStatusIndicatorDabaseNotConnected"

Image
{
id: ueStatusIndicatorCurrentImage

smooth: true

fillMode: Image.PreserveAspectFit

width: 96
height: 96

sourceSize.width: 96
sourceSize.height: 96
} // Image

Connections
{
target: ueStatusIndicator

onUeSignalDatabaseConnectionChanged:
{
state=(status===UeTypeDatabaseConnectionStatus.CON NECTED):
"ueStatusIndicatorDabaseConnected"?
"ueStatusIndicatorDabaseNotConnected"
}
} // Connections

states:
[
State
{
name: "ueStatusIndicatorDabaseConnected"

PropertyChanges
{
target: ueStatusIndicatorCurrentImage
source: ueParamImageStatusOn
} // PropertyChanges
}, // State

State
{
name: "ueStatusIndicatorDabaseNotConnected"

PropertyChanges
{
target: ueStatusIndicatorCurrentImage
source: ueParamImageStatusOff
} // PropertyChanges
} // State
] // states
} // Item

anda_skoa
17th September 2015, 14:20
Ok, so there is no way to handle this via signal/slot

Of course there is, I just pointing out that in this case it is unnecessary since it can be done much more elegantly using a property binding.

But if you insist on doing it the ugly way


Connections
{
target: ueApplicationStatus

onUeSignalDatabaseConnectionChanged:
{
state=(status===UeTypeDatabaseConnectionStatus.CON NECTED) ?
"ueStatusIndicatorDabaseConnected" : "ueStatusIndicatorDabaseNotConnected"
}
} // Connections

- you want to handle the signal of the ueApplicationStatus object (which has a nice property that you should be using instead)
- the syntax for the conditional assignment is:
condition ? valueIf : valueElse

Cheers,
_

MarkoSan
18th September 2015, 10:11
Of course there is, I just pointing out that in this case it is unnecessary since it can be done much more elegantly using a property binding.

But if you insist on doing it the ugly way


Connections
{
target: ueApplicationStatus

onUeSignalDatabaseConnectionChanged:
{
state=(status===UeTypeDatabaseConnectionStatus.CON NECTED) ?
"ueStatusIndicatorDabaseConnected" : "ueStatusIndicatorDabaseNotConnected"
}
} // Connections

- you want to handle the signal of the ueApplicationStatus object (which has a nice property that you should be using instead)
- the syntax for the conditional assignment is:
condition ? valueIf : valueElse

Cheers,
_
Hmm, now I have following code - UeStatus header:


#ifndef UESTATUS_H
#define UESTATUS_H

#include <QObject>
#include <QList>

#include "../core/uetypes.h"
#include "../core/uedatabaseconnectionstatus.h"

class UeStatus : public QObject
{
Q_OBJECT

Q_PROPERTY(UeTypeLoggedUsers* m_ueLoginCandidates
READ ueLoginCandidates
WRITE ueSetLoginCandidates
NOTIFY ueSignalLoginCandidatesChanged)
Q_PROPERTY(UeTypeLoggedUsers* m_ueLoggedUsers
READ ueLoggedUsers
WRITE ueSetLoggedUsers
NOTIFY ueSignalLoggedUsersChanged)
Q_PROPERTY(UeDatabaseConnectionStatus::UeTypeDatab aseConnectionStatus m_ueDatabaseConnectionStatus
READ ueDbConnectionStatus
WRITE ueSetDbConnectionStatus
NOTIFY ueSignalDatabaseConnectionChanged)

private:
UeTypeLoggedUsers* m_ueLoginCandidates;
UeTypeLoggedUsers* m_ueLoggedUsers;
UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus m_ueDatabaseConnectionStatus;

public:
explicit UeStatus(QObject *parent = 0);
~UeStatus();

inline UeTypeLoggedUsers* ueLoginCandidates() const
{ return this->m_ueLoginCandidates; }
inline UeTypeLoggedUsers* ueLoggedUsers() const
{ return this->m_ueLoggedUsers; }
inline UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus ueDbConnectionStatus() const
{ return this->m_ueDatabaseConnectionStatus; }

inline void ueSetLoginCandidates(UeTypeLoggedUsers* const m_ueLoginCandidates)
{ this->m_ueLoginCandidates=m_ueLoginCandidates; }
inline void ueSetLoggedUsers(UeTypeLoggedUsers* const loggedUsers)
{ this->m_ueLoggedUsers=loggedUsers; }
inline void ueSetDbConnectionStatus(const UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus& status)
{ this->m_ueDatabaseConnectionStatus=status; }

signals:
void ueSignalLoginCandidatesChanged();
void ueSignalLoggedUsersChanged();
void ueSignalDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus& newStatus);

public slots:
void ueSlotDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus& newStatus);
};

#endif // UESTATUS_H

and its implementation:


#include "uestatus.h"

UeStatus::UeStatus(QObject *parent)
: QObject(parent)
{
this->ueSetLoginCandidates(new UeTypeLoggedUsers());
this->ueSetLoggedUsers(new UeTypeLoggedUsers());
//this->ueSetDbConnectionStatus(NOT_CONNECTED);
} // constructor

UeStatus::~UeStatus()
{
delete this->ueLoginCandidates();
delete this->ueLoggedUsers();
} // destructor

void UeStatus::ueSlotDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnecti onStatus& newStatus)
{
this->ueSetDbConnectionStatus(newStatus);

emit this->ueSignalDatabaseConnectionChanged(newStatus);
} // ueSignalDatabaseConnectionChanged

and here is item:


import QtQuick 2.0

import si.mikroelektronika 1.0

Item
{
id: ueStatusIndicator

property string ueParamImageStatusOn
property string ueParamImageStatusOff

state: "ueStatusIndicatorDabaseNotConnected"

Image
{
id: ueStatusIndicatorCurrentImage

smooth: true

fillMode: Image.PreserveAspectFit

width: 96
height: 96

sourceSize.width: 96
sourceSize.height: 96
} // Image

Connections
{
target: ueApplicationStatus

onUeSignalDatabaseConnectionChanged:
{
state=(newStatus===UeTypeDatabaseConnectionStatus. CONNECTED)?
"ueStatusIndicatorDabaseConnected":
"ueStatusIndicatorDabaseNotConnected"
}
} // Connections

states:
[
State
{
name: "ueStatusIndicatorDabaseConnected"

PropertyChanges
{
target: ueStatusIndicatorCurrentImage
source: ueParamImageStatusOn
} // PropertyChanges
}, // State

State
{
name: "ueStatusIndicatorDabaseNotConnected"

PropertyChanges
{
target: ueStatusIndicatorCurrentImage
source: ueParamImageStatusOff
} // PropertyChanges
} // State
] // states
} // Item

and this code combo DOES NOT work, i.e., signal code does not get executed in UeStatusIndicator. What did we still miss?

anda_skoa
18th September 2015, 10:49
Try changing the signal's signature to have an int instead of the enum.

Or use the property binding approach.

Cheers,
_

MarkoSan
18th September 2015, 11:05
Try changing the signal's signature to have an int instead of the enum.

Or use the property binding approach.

Cheers,
_
Ok, but binding works only in QML=>C++ way, not vice versa, as seen in http://doc.qt.io/qt-5/qml-qtqml-binding.html#details, what I need is update QML element based on C++ class member value:

Detailed Description

Binding to an Inaccessible Property

Sometimes it is necessary to bind to a property of an object that wasn't directly instantiated by QML - generally a property of a class exported to QML by C++. In these cases, regular property binding doesn't work. Binding allows you to bind any value to any property.

For example, imagine a C++ application that maps an "app.enteredText" property into QML. You could use Binding to update the enteredText property like this.

TextEdit { id: myTextField; text: "Please type here..." }
Binding { target: app; property: "enteredText"; value: myTextField.text }
and the last sentence in the mentioned docs states:
Whenever the text in the TextEdit is updated, the C++ property will be updated also.
Can you show me please what did you plan to do, since I need contra scenarium.

Sincerely,
Marko

anda_skoa
18th September 2015, 13:09
Ok, but binding works only in QML=>C++ way, not vice versa

No



as seen in http://doc.qt.io/qt-5/qml-qtqml-binding.html#details

Which is totally irrelevant here since you are not trying to bind a property of a C++ object to an expression.



what I need is update QML element based on C++ class member value

Indeed, which is just a plain and simple property binding.
The "state" property gets bound to an expression using the context property object's "connection status" property.

Cheers,
_

MarkoSan
18th September 2015, 13:40
No


Which is totally irrelevant here since you are not trying to bind a property of a C++ object to an expression.


Indeed, which is just a plain and simple property binding.
The "state" property gets bound to an expression using the context property object's "connection status" property.

Cheers,
_
Can you give me example please?!?!?!

anda_skoa
18th September 2015, 14:35
You've done that numerous times already, I am sure :)


state: ueApplicationStatus.m_ueDatabaseConnectionStatus === UeTypeDatabaseConnectionStatus.CONNECTED ?
"ueStatusIndicatorDabaseConnected" : "ueStatusIndicatorDabaseNotConnected"


Cheers,
_