I have an QObject with properties accessible from QML.
Something like:
Class C : public QObject {
Q_OBJECT
public:
explicit C(QObject * parent = nullptr);
Q_PROPERTY(QString ro_text READ ro_text WRITE setRo_text NOTIFY ro_textChanged)
};
Is it possible to make the setter(setRo_text) "private", so the property cannot by modified from QML, but can still be set from C++ code(inside the class)?
if you don't want it to be modified from QML then don't declare the WRITE, and create a method that every time the property changes it emits the signal, the setter method can be public or private but it can't be accessed in QML
class C: public QObject{
Q_OBJECT
Q_PROPERTY(QString ro_text READ ro_text NOTIFY ro_textChanged)
public:
C(QObject *parent=nullptr): QObject(parent){
}
QString ro_text() const {
return m_ro_text;
}
Q_SIGNALS:
void ro_textChanged();
private:
void setRo_text(const QString & text){
if(m_ro_text == text)
return;
m_ro_text = text;
Q_EMIT ro_textChanged();
}
QString m_ro_text;
};
Related
I have a very simple class with 2 properties; key and value:
KeyValue.h:
class KeyValue : public QObject
{
Q_OBJECT
Q_PROPERTY(QString key READ getKey WRITE setKey NOTIFY keyChanged)
Q_PROPERTY(QString value READ getValue WRITE setValue NOTIFY valueChanged)
public:
KeyValue(const QString& key, const QString& value, QObject* parent = 0);
signals:
void keyChanged();
void valueChanged();
private:
QString _key;
QString _value;
QString getKey() const;
QString getValue() const;
void setKey(const QString& key);
void setValue(const QString& value);
};
Q_DECLARE_METATYPE(KeyValue)
In another class I would like a property containing a list of KeyValue objects, so I can use this list as a model in QML.
Controller.h
class Controller : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<KeyValue*> items READ getItems NOTIFY itemsChanged)
public:
explicit Controller(QObject* parent = 0);
signals:
void itemsChanged();
private:
QList<KeyValue*> getItems() const;
};
I want to be able to use this in QML the following way:
import QtQuick 2.7
import customqml 1.0
Item{
Controller{
id: controller
}
Repeater{
model: controller.items
Text{
text: modelData.key + ": " + modelData.value
}
}
}
Both classes are registered in my main.cpp file:
qmlRegisterType<KeyValue>("customqml", 1, 0, "KeyValue");
qmlRegisterType<Controller>("customqml", 1, 0, "Controller");
The above code does not work, bacause I apparently can't expose a QList to QML directly. I have tried using QAbstractItemModel and QQmlListProperty, but I was unable to get it to work. Can anyone point me in the right direction?
My primary issues are the type of the items property in the Controller class and the return value of the getItems method.
I'm using Qt 5.9 if that makes any difference.
Note:
The getters and setters are generally public except for exceptions so move it to the public part
The classes that inherit from QObject do not need QMetaType because when you want to transfer data of that class the pointers are used.
Not all data types are supported by QML through Q_PROPERTY, so a possible solution is to export through known classes such as
QList<QObject *>:
class Controller : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<QObject *> items READ getItems NOTIFY itemsChanged)
public:
explicit Controller(QObject *parent = nullptr);
QList<QObject *> getItems() const;
signals:
void itemsChanged();
private:
QList<KeyValue *>key_values_list;
};
...
QList<QObject *> Controller::getItems() const
{
QObjectList l;
for(auto e: key_values_list)
l << e;
return l;
}
QVariantList:
class Controller : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList items READ getItems NOTIFY itemsChanged)
public:
explicit Controller(QObject *parent = nullptr);
QVariantList getItems() const;
signals:
void itemsChanged();
private:
QList<KeyValue *>key_values_list;
};
...
QVariantList Controller::getItems() const
{
QVariantList l;
for(auto e: key_values_list)
l.append(QVariant::fromValue(e));
return l;
}
Other options is to implement a model, the following example shows only a read-only model:
keyvaluemodel.h
#ifndef KEYVALUEMODEL_H
#define KEYVALUEMODEL_H
#include "keyvalue.h"
#include <QAbstractListModel>
class KeyValueModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit KeyValueModel(QObject *parent = nullptr)
: QAbstractListModel(parent)
{
key_values_list = {new KeyValue{"k", "v"}, new KeyValue{"k2", "v2"}};
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
if (parent.isValid())
return 0;
return key_values_list.length();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (!index.isValid())
return QVariant();
if(index.row() >= 0 && index.row() < rowCount())
return QVariant::fromValue(key_values_list[index.row()]);
return QVariant();
}
private:
QList<KeyValue* >key_values_list;
};
#endif // KEYVALUEMODEL_H
class Controller : public QObject
{
Q_OBJECT
Q_PROPERTY(KeyValueModel* items READ getItems NOTIFY itemsChanged)
public:
explicit Controller(QObject *parent = nullptr);
KeyValueModel* getItems() const;
signals:
void itemsChanged();
private:
KeyValueModel *model;
};
...
Text{
text: display.key + ": " + display.value
}
...
And in a similar way you can implement a QQmlListProperty, in the docs there are many examples.
If you want a sophisticated model with adding/deleting objects and altering data you should look into subclassing QAbstractListModel.
As a simple but less flexible way you can use a QVariantList and make your Controller class a value type. You need:
The macro Q_DECLARE_METATYPE(Controller) at the end of the Controller header file.
A copy constructor for Controller
A default constructor for Controller
Q_PROPERTY Type and return value are then QVariantList.
I have this class I intended to use in the context of a qml engine so in order to use property binding I setted up these Q_PROPERY macros. I want to use the MEMBER keyword and have the notify signal emitted automatically.
class InterfaceBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view MEMBER m_current_view NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view MEMBER m_future_view NOTIFY sFutureViewChanged)
public:
explicit InterfaceBackend(QObject *parent = 0);
~InterfaceBackend();
quint8 getCurrentView() { return this->m_current_view; }
quint8 getFutureView() { return this->m_future_view; }
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language);
private slots:
signals:
void sCurrentViewChanged(quint8 current_view);
void sFutureViewChanged(quint8 future_view);
};
InterfaceBackend::InterfaceBackend(QObject *parent) : QObject(parent)
{
this->setObjectName("backend");
QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() {qDebug() << "sFutureViewChanged";});
this->m_current_view=1;
this->m_future_view=1;
}
InterfaceBackend::~InterfaceBackend()
{
}
void InterfaceBackend::onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->m_future_view=2;
}
qt docs say:
A NOTIFY signal is optional. If defined, it should specify one existing signal in that class that is emitted whenever the value of the property changes. NOTIFY signals for MEMBER variables must take zero or one parameter, which must be of the same type as the property. The parameter will take the new value of the property. The NOTIFY signal should only be emitted when the property has really been changed, to avoid bindings being unnecessarily re-evaluated in QML, for example. Qt emits automatically that signal when needed for MEMBER properties that do not have an explicit setter
But whenever I call the slot the signals never gets called nor the property is updated in the qml model, what's wrong!?
To give an answer that is technically more accurate:
The MEMBER in Q_PROPERTY will tell the moc (Meta object compiler) that when accessing the property via the meta object it should use the member directly instead of a getter or setter method. So the moc will the generate a setter method internally that sets the member and emits the signal - it basically just does the work of writing getters/setters for you. Since changing a member needs to emit the change signal, this is automatically done when the property is written from the meta object system. So, calling:
backend->setProperty("future_view", future_view);
will correctly emit the changed signal. This is the only guarantee that is given when using MEMBER. Changes, that are done via the meta property will trigger the change signal. This means if you would set future_view from QML directly, without the onLanguageSelected method, it would actually work.
In your example however you directly write a value to the member inside a special method - This will not trigger the signal automatically! (I mean, how should Qt even know you did that). So what you need to do is whenever you change the value of your member you need to emit the change signal yourself:
void onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->m_future_view=2;
emit sFutureViewChanged();
}
Edit: If you were trying prevent the properties from beeing written directly from QML, using MEMBER will not work! Use a getter instead and only register the getter with the property. Use the same code as above to write and change the properties:
Q_PROPERTY(quint8 future_view READ futureView NOTIFY sFutureViewChanged)
As you can see in this article:
New keyword in Q_PROPERTY: MEMBER let you bind a property to a class member without requiring to have a getter or a setter.
So you should remove your getters, and your result code will look like this
class InterfaceBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view MEMBER m_current_view NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view MEMBER m_future_view NOTIFY sFutureViewChanged)
public:
explicit InterfaceBackend(QObject *parent = 0)
: QObject(parent)
{
this->setObjectName("backend");
QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() { qDebug() << "sFutureViewChanged";});
this->m_current_view=1;
emit sCurrentViewChanged();
this->m_future_view=1;
emit sFutureViewChanged();
}
~InterfaceBackend() = default;
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language) {
this->m_selected_language=language;
this->m_future_view=2;
emit sFutureViewChanged();
}
signals:
void sCurrentViewChanged();
void sFutureViewChanged();
};
Every Q_PROPERTY must have READ public method and WRITE public slot, also the signal will never automatically emited, you should emit it whenever MEMBER change.
class InterfaceBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view MEMBER m_current_view READ currentView WRITE setCurrentView NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view MEMBER m_future_view READ futureView WRITE setFutureView NOTIFY sFutureViewChanged)
public:
explicit InterfaceBackend(QObject *parent = 0);
~InterfaceBackend();
quint8 currentView() const
{
return m_current_view;
}
quint8 futureView() const
{
return m_future_view;
}
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language);
void setCurrentView(quint8 current_view)
{
if (m_current_view == current_view)
return;
m_current_view = current_view;
emit sCurrentViewChanged(m_current_view);
}
void setFutureView(quint8 future_view)
{
if (m_future_view == future_view)
return;
m_future_view = future_view;
emit sFutureViewChanged(m_future_view);
}
private slots:
signals:
void sCurrentViewChanged(quint8 current_view);
void sFutureViewChanged(quint8 future_view);
};
InterfaceBackend::InterfaceBackend(QObject *parent) : QObject(parent)
{
this->setObjectName("backend");
QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() {qDebug() << "sFutureViewChanged";});
this->m_current_view=1;
this->m_future_view=1;
}
InterfaceBackend::~InterfaceBackend()
{
}
void InterfaceBackend::onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->m_future_view=2;
}
Spending some time, I got it working as I need, althought this is EXTREMELY UGLY and BY NO MEANS DECLARATIVE I think it is the only way to achive double binding, and maybe it will be helpful for other people approaching the problem. Note this is not an accepted answer, I still hope someone somewhere, perhaps in the future if and when Qt will be updated, could come up with a far more elegant solution.
Main disadvantages: longer and verbose code, one has to use setters everywhere and (try not forget)
The property system doesn't do anything automatically in the end, just states wich properties are exposed and wich are their getters/setters and change signals
c++
class InterfaceController : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view READ getCurrentView WRITE setCurrentView NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view READ getFutureView WRITE setFutureView NOTIFY sFutureViewChanged)
public:
explicit InterfaceController(QObject *parent = 0);
//getters
quint8 getCurrentView() { return this->m_current_view; }
quint8 getFutureView() { return this->m_future_view; }
//setters
Q_INVOKABLE void setCurrentView(quint8 current_view) { if(this->m_current_view!=current_view) {this->m_current_view=current_view; emit sCurrentViewChanged(this->m_current_view);} }
Q_INVOKABLE void setFutureView(quint8 future_view) { if(this->m_future_view!=future_view) {this->m_future_view=future_view; emit sFutureViewChanged(this->m_future_view);} }
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language);
private slots:
signals:
void sCurrentViewChanged(quint8 current_view);
void sFutureViewChanged(quint8 future_view);
};
InterfaceController::InterfaceController(QObject *parent) : QObject(parent)
{
this->m_current_view=1;
this->m_future_view=1;
}
void InterfaceController::onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->setFutureView(2);
}
QML
id: root
property int current_view: 1
property int future_view: 1
Connections {
target: root
onCurrent_viewChanged: { backend.setCurrentView(current_view); }
onFuture_viewChanged: { backend.setFutureView(future_view); }
}
Connections {
target: backend
onSCurrentViewChanged: { if(root.current_view!=current_view) {root.current_view=current_view;} }
onSFutureViewChanged: { if(root.future_view!=future_view) {root.future_view=future_view;} }
}
I have a very simple base class which defines a normal Q_PROPERTY with READ and NOTIFY (WRITE is not in all dereived implementations possible).
class BaseClass : public QObject {
Q_OBJECT
Q_PROPERTY(QStringList someEntries READ someEntries NOTIFY someEntriesChanged)
public:
explicit BaseClass(QObject *parent = Q_NULLPTR) : QObject(parent) {}
virtual ~BaseClass() {}
virtual QStringList someEntries() const = 0;
void processEntries() {
for(auto entry : someEntries()) {
//process entry
}
}
Q_SIGNALS:
void someEntriesChanged(const QStringList &someEntries);
};
Now I have 2 dereiving classes, of which one supports only one entry (SingleItem) and the other one supports multiple entries (MultiItem).
SingleItem converts the one QString-member to a QStringList when requested and allows setting it using the differently called property (theEntry):
class SingleItem : public BaseClass {
Q_OBJECT
Q_PROPERTY(QString theEntry READ theEntry WRITE setTheEntry NOTIFY theEntryChanged);
public:
explicit SingleItem(QObject *parent = Q_NULLPTR) : BaseClass(parent) {}
virtual ~SingleItem() {}
const QString &theEntry() const { return m_theEntry; }
void setTheEntry(const QString &theEntry) {
if(m_theEntry != theEntry)
Q_EMIT theEntryChanged(m_theEntry = theEntry);
}
// BaseClass interface
QStringList someEntries() const Q_DECL_OVERRIDE
{ return QStringList { m_theEntry }; }
Q_SIGNALS:
void theEntryChanged(const QString &theEntry);
private:
QString m_theEntry;
};
MutliItem stores a QStringList (for someEntries). I would like to write into that QStringList using the property defined in BaseClass. But I dont know how to add the WRITE option to the already defined Q_PROPERTY.
class MultiItem : public BaseClass {
Q_OBJECT
//Somehow add the setSomeEntries method to the property?
public:
explicit MultiItem(QObject *parent = Q_NULLPTR) : BaseClass(parent) {}
virtual ~MultiItem() {}
void setSomeEntries(const QStringList &someEntries) {
if(m_someEntries != someEntries)
Q_EMIT someEntriesChanged(m_someEntries = someEntries);
}
// BaseClass interface
QStringList someEntries() const Q_DECL_OVERRIDE{ return m_someEntries; }
private:
QStringList m_someEntries;
};
I cannot add the WRITE option in BaseClass because it would violate the meaning. Not every object of BaseClass allows setting that property but objects of MultiItem should be written only using that property.
Is this somehow possible without declaring a WRITE option to a virtual ... = 0 in BaseClass that just logs a warning when called in SingleItem?
Im using a QML frontend for my C++ App which worked fine so far. However, I planned to tidy up my code and split functions into smaller classes
At first, my Property decleration looked like this:
class mainBoard : public QObject
{
Q_OBJECT
Q_PROPERTY(double baroAltitude MEMBER baroAltitude NOTIFY pressureChanged)
public:
explicit mainBoard(QObject *parent = 0);
void start();
private:
double baroAltitude = 0;
signals:
void pressureChanged();
};
Now, I do have this external class, with my getter method.
#include "pressuresensor.h"
class mainBoard : public QObject
{
Q_OBJECT
Q_PROPERTY(double baroAltitude READ pressureSensors.getBaroAltitude NOTIFY pressureSensors.pressureChanged)
public:
explicit mainBoard(QObject *parent = 0);
void start();
private:
pressureSensor pressureSensors;
};
But now, all I get is:
mainboard.h:25: Parse error at "pressureSensors"
error: [moc_mainboard.cpp] Error 1
Is there a better, or correct (because its working :D ) way for it?
thanks!
Q_PROPERTY does not support getters/setters methods which are not part of the class in question.
If you really want to keep the pressureSensor class you have to provide getters/setters in the mainBoard class and forward the calls.
class mainBoard : public QObject
{
Q_OBJECT
Q_PROPERTY(double baroAltitude READ getBaroAltitude)
public:
double getBaroAltitude() const {
return pressureSensors.getBaroAlitude();
}
private:
pressureSensor pressureSensors;
};
Is it possible to make MyObject be always equal (one same instance) in all it's qml definitions?
C++:
class MyObject : public QObject {
Q_OBJECT
Q_DISABLE_COPY(MyObject)
Q_PROPERTY(QString test READ test NOTIFY testChanged)
public:
explicit MyObject(QObject *parent = 0);
signals:
void testChanged();
private:
QString test() const {
return _test;
}
QString _test;
};
QML:
Item {
MyObject { id: myObject1 }
MyObject { id: myObject2 }
}
I want myObject1 to be equal myObject2. Some kind of singleton (but no qmlRegisterSingletonType)
I can interpret your question as if you want more than one entry of MyObject in QML code referring to the same C++ object. You also know what singleton is. How about the wrapper over the singleton that you can use with QML like:
class MyObject : public QObject {
Q_OBJECT
Q_DISABLE_COPY(MyObject)
Q_PROPERTY(QString test READ test NOTIFY testChanged)
public:
explicit MyObject(QObject *parent = 0);
signals:
void testChanged();
private:
QString test() const {
return MySingleton::instance().test();
}
// QString _test; // this supposed to be implemented in MySingleton
};
Or I in my application for many different types of communication between C++ and QML use some kind of MessageBoard from the article Exposing Attributes of C++ Types to QML. That one is even more convenient considering many uses.