HeinriG HeinriG - 1 month ago 9
C++ Question

Emitting signal not updating the values in GUI

I'm trying to make a very basic timetable app just to learn the basics of Qt. I've researched this quite a bit and cant seem to get an answer to my problem, I emit the signal when I change the value of my subject but the emitting signal does not update the GUI. It definitely is changing the value in the code but the QML GUI does not update it during run time.

I have a class called Day with the following Q_PROPERTY's:

Q_PROPERTY(Period *getPeriod READ getPeriod WRITE setPeriod NOTIFY periodChanged)
Q_PROPERTY(QString getDay READ getDay WRITE setDay NOTIFY dayChanged)


and a member to hold the periods

Period *m_period = new Period[10]; //Of type Period (my other class) which holds my subject string


I also have getters and setters for the days and periods(as seen in the Q_PROPERTY) and these two for setting/getting the subject:

Q_INVOKABLE QString getSubject(int t){
return m_period[t].getSub();
};
Q_INVOKABLE void setSubject(int t, QString subj){
m_period[t].setSub(subj);
emit periodChanged();
};


With the following signals:

void periodChanged();
void dayChanged();


I also have a class called Period with the following Q_PROPERTY:

Q_PROPERTY(QString getSub READ getSub WRITE setSub NOTIFY subChanged)


and a member to hold the subject names:

QString subject;


My Period class hold the functions called in day to actually change the QString subject:

QString getSub(){
return subject;
};
void setSub(QString sub){
subject = sub;
emit subChanged();
};


With the following signal:

void subChanged();


So surely when the subject gets changed using setSubject() in my QML, shouldn't it be emitting the periodChanged() and the subChanged() signals, which should update the GUI? I thought it would but it doesn't seem to be working.

For reference, this is basically how I implemented it in QML:

Label { text: monday.getSubject(2) } //How I display the Subject, the parameter being the period number

Button{
text: "Enter"
onClicked:{
monday.setSubject(2, "RANDOM_TEXT") //How I set the subject at run time, with the first argument being the period number and second the text I want to change it to
}


Here are the main and class files:

main.cpp

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

qmlRegisterType<Day>("Timetable", 1,0, "Day");
qmlRegisterType<Period>("Timetable", 1,0, "Period");

QQmlApplicationEngine engine;

Day monday;
engine.rootContext()->setContextProperty("monday", &monday);

engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QQmlComponent component(&engine, QUrl::fromLocalFile("main.qml"));
component.create();

return app.exec();
}


Day.h

class Day : public QObject
{
Q_OBJECT
Q_PROPERTY(Period *getPeriod READ getPeriod WRITE setPeriod NOTIFY periodChanged)


private:
Period *m_period = new Period[10];

public:
Day(QObject *parent = 0);
~Day();

Period* getPeriod();
Q_INVOKABLE void setPeriod(Period *p);

Q_INVOKABLE QString getSubject(int t){
return m_period[t].getSub();
};
Q_INVOKABLE void setSubject(int t, QString subj){
m_period[t].setSub(subj);
emit periodChanged();
};

signals:
void periodChanged();
};


Period.h

class Period: public QObject
{
Q_OBJECT
Q_PROPERTY(QString getSub READ getSub WRITE setSub NOTIFY subChanged)

private:
QString subject;

public:
Period(QObject *parent = 0);
~Period();

QString getSub(){
return subject;
};
void setSub(QString sub){
subject = sub;
emit subChanged();
};

signals:
void subChanged();
};


main.qml

ApplicationWindow {
id: mainWindow
visible: true
title: "Timetable App"
property int margin: 11
width: mainLayout.implicitWidth + 2 * margin
height: mainLayout.implicitHeight + 2 * margin
minimumWidth: 800
minimumHeight: 600

ColumnLayout {
id: mainLayout
anchors.fill: parent
anchors.margins: margin

GroupBox{

id: timetable
title: "Timetable"
Layout.fillWidth: true
Layout.fillHeight: true

GridLayout {
id: gridLayout
rows: 11
flow: GridLayout.TopToBottom
anchors.fill: parent
Layout.fillWidth: true
Layout.fillHeight: true

Label { }
Label { text: "8:00" }
Label { text: ...} // Added Labels for times from 8:00 to 17:00

Label { text: "Monday" }
Label { text: monday.getSubject(0) }
Label { text: monday.getSubject(1) }
Label { text: ...} // Added Labels to display subjects for monday at different times, also did the same for the rest of the week
}
}

RowLayout{
Button{
text: "Enter"
onClicked:{
monday.setSubject(1, "RANDOM_TEXT") // Set the second period of monday to "RANDOM_TEXT"
console.log(monday.getSubject(1)) // To check if actually set
}
}
}
}
}

Answer

Your problem is that you are retrieving values via method calls in QML, not via property bindings.

A QML code like this should update

text: monday.getPeriod.getSub

Obviously calling properties "getSomething" is a bit weird.

Now, your m_period suggests that a single Day object will have more than one Period, which would suggest that you might want a list property instead of a single Period object. Something like

Q_PROPERTY(QList<Period*> periods READ getPeriods NOTIFY periodsChanged);

Using that from QML a bit like this

text: monday.periods[0].getSub

Or even with e.g. a Repeater

Label { text: "Monday" }
Repeater {
    model: monday.periods

    Label {
        text: modelData.getSub
    }
}

Not related to your update problem but also important to consider: NOTIFY signals trigger binding updates, so you don't want to emit them unnecessarily. I.e. setters that emit those should first check if the new value is actually different than the old value and only emit if they are.

Comments