How to expose dynamically amount of data to QML - c++

I have an app were I need to fetch random questions from a database and expose them to qml dynamically.
So I created a class to store each dataset:
class Question : public QObject
{
Q_OBJECT
Q_PROPERTY(int id READ id)
Q_PROPERTY(QString askedQuestion READ askedQuestion)
public:
Question(int id,
QString askedQuestion);
int getId() const;
QString getAskedQuestion() const;
private:
int mId;
QString mAskedQuestion;
};
And fill them in annother class. In reality it is derrived from an SQLDatabaseModel:
class QuestionGenerator : public QObject
{
Q_OBJECT
public:
explicit QuestionGenerator(QObject *parent = nullptr);
Q_INVOKABLE QVector<Question> getRandomQuestions(int count) const
{
// simplified in reality we fetch random questions from a database.
// the point is we need to add Questions to the vector
// but this does not work since QObject based items cannot get copied
QVector<Question> questions;
questions.reserve(count);
// add questions to vector
return questions;
}
};
I want to expose Question to QML to use the data from Question there so I need to derive it from QObject.
When I fetch the Questions randomly in QuestionGenerator it does not work because QVector does net the not supported copy constructor of QObject.
So how can I fix this?
Again what I want:
Fetch n Questions in C++ and expose them to QML so I can use the data to display.

You can't use QVector<Question> because QObjects are not copyable. But you can use a QVector<Question*> (note the pointer). You can make as many copies as you want of a pointer.
There's other ways to solve your problem too. Like a QAbstractListModel. But the easy solution is just a pointer.

Related

QML/QT How to convert object from C++ to QML?

It took me a while, but eventually I managed to convert a JavaScript/QML POJO to a custom object not derived from QObject.
I think it's easier to understand my issue with a working example. So let's start with this:
struct SomeType { /* Just a plain struct that does not derive from QObject! */ };
SomeType FooFactory::convertQMLToSomeType(const QJSValue& val) {
SomeType result = /*... some kind of conversion takes place here ... */
return result;
}
void FooFactory::registerTypes(QQmlEngine& engine) {
QMetaType::registerConverter<QJSValue, SomeType>(FooFactory::convertQMLToSomeType);
}
What this does is it registers a converter for the transformation of QSValue to SomeType. So now, whenever I do something like this in QML
my_prop = { "foo": "some plain javascript object" };
assuming my_prop is exposed like so in the corresponding C++ class:
Q_PROPERTY(SomeType my_prop MEMBER _myProp);
SomeType _myProp;
// Somewhere else outside the class, this is needed for registering the converter
Q_DECLARE_METATYPE(SomeType);
the string is implicitly converted into a SomeType without the need of doing things manually.
..
Great!
But what about the opposite direction of the conversion? I Need QML to deal with strings, not QVariant(SomeType) objects (QT always uses QVariant wrappers internally to store user defined types when dealing with the meta system).
I already tried registering an inverse converter like this:
QMetaType::registerConverter<SomeType, QJSValue>(FooFactory::convertBackToQML);
or this
QMetaType::registerConverter<QVariant(SomeType), QJSValue>(FooFactory::convertBackToQML);
but none of these approaches work. I believe the second line is quite promising, but I wasn't even able to compile that one due to problems with registering the static meta type.
So, how would I solve this? As a short reminder, I am not able to derive SomeType from QObject, and yes, I am aware that this is the most common way to do these kinds of things.
Does anyone have an idea? Or am I barking up the wrong tree? Many thanks in advance!
This may not be what you want, but it might also be your only option. You can create a QObject wrapper class around your struct. Just create properties for whatever values in the struct you want to expose.
class SomeWrapper : public QObject
{
Q_OBJECT
Q_PROPERTY(QString someData READ someData WRITE setSomeData NOTIFY someDataChanged)
public:
explicit SomeWrapper(QObject *parent = nullptr) : QObject(parent) {}
QString someData() { return m_struct.someData; }
void setSomeData(QString data)
{
if (data != m_struct.someData)
{
m_struct.someData = data;
emit someDataChanged();
}
}
signals:
void someDataChanged();
private:
SomeType m_struct;
};
If you want to operate with your structure as with a plain JavaScript object, then another option may be using QVariantMap as a type for your property. Then you could define the needed conversions in getter and setter of your property:
class Whichever : public QObject {
Q_OBJECT
Q_PROPERTY(QVariantMap my_prop READ myProp WRITE setMyProp)
QVariantMap myProp() const {
QVariantMap map;
map["some_field"] = _myProp.someField;
// Fill the other fields as needed.
return map;
}
void setMyProp(const QVariantMap& map) {
_myProp.someField = map["some_field"].toString();
// Fill the other _myProp fields as needed.
}
};
Conversions between QVariantMap and the actual JavaScript objects would be handled by QML engine automatically. Nested JavaScript objects should also be possible by nesting the corresponding QVariantMaps.
Of course, this option makes property declaration not so explicit about which type does it correspond to, and perhaps it will require a bit more boilerplate code if you need to declare multiple properties of this type (you can define and use your own macro for that though if needed). But this is probably one of the easiest ways to achieve what you have described.

Qt and C++: Overwrite or Expand Class

I want to add some properties (like an ID) to a QPushButton. Therefore, I need to expand or overwrite the class Q_WIDGETS_EXPORT QPushButton : public QAbstractButton
How do I do that?
Thanks for the help.
you dont need to extend the class to just put an id in it ... instead make use of the property system.
as specified in the official doc here:
A property can be read and written using the generic functions QObject::property() and QObject::setProperty(), without knowing anything about the owning class except the property's name.
you just have to do:
ui->myButton->setProperty("Id", 123456);
can also be another object e.g a string (or even your own class if you define it to do that)
ui->myButton->setProperty("_name", "123456");
to read the property is the method property() there for you but read the doc because you get a QVariant as return example:
QVariant(int, 123456)
It really depends on the use case. There is no problem (and often the intended way) in inheriting from Qt-(Widget) Classes (correct me, if I am wrong).
So you could do:
class MyQPushButton : public QPushButton
{
Q_OBJECT
public:
MyQPushButton() : QPushButton(...) {}
private:
int ID = -1;
}
Qt has a very good documentation and you can look at the sources to see what to override.
You could also extend a new class with QPushButton, but than you always have to deal with the QPushButton reference in your class, if you want e.g. connect something. In the inherited class you can connect the slots and so on. But for example you could do this:
class MyQPushButton
{
public:
MyQPushButton() {}
const QPushButton& const GetQPushButton() { return pushButton; }
const QPushButton* const GetQPushButtonPtr() { return &pushButton; }
private:
QPushButton pushButton;
int ID = -1;
}
There is no right and wrong. But I would use the inheritance for Qt-classes.

Binding instance of C++ object to QML object

I'm new to Qt, and have written a basic application which has one class which inherits from QObject and is bound to a QML file.
I now want that class to contain a Vector of objects (let's say from a class Customers), which contains some data, such as a QString for name, etc.
To make life easier I'll create these objects manually in main, and place some text fields in my QML file.
I now want to be able to bind specific objects to specific text fields in the QML file, such that when a value changes, the value in the text field updates.
How can this be done? It looks like QML statically calls methods of the classes it's bound to, instead of on an assigned object.
I feel like QAbstractList may have some use here, but not too sure. Would rather not have to inherit from anything for my Customers class.
EDIT:
I think I may be able to do what I want with a QObjectList-based Model (https://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html). I notice it says at the bottom that "There is no way for the view to know that the contents of a QList has changed. If the QList changes, it is necessary to reset the model by calling QQmlContext::setContextProperty() again."
Does this mean that if a value inside DataObject (such as name) changes, the model has to be reset, or only when the Qlist itself changes (i.e. new item added, or item deleted)? If the latter, I would think this should be fairly easy to maintain as I would only need to set the context property whenever anything is added or deleted.
This may be usefull if you want to process raw QObject instance in QML script. You can append properties to Element class and modify them from qml.
class Element : public QObject {
Q_OBJECT
private:
QString type;
QLinkedList<Element*> child;
public:
explicit Element(QString type, QLinkedList<Element*> child);
virtual ~Element();
public:
QLinkedList<Element*> getChild() const;
QString getType() const;
public:
static void printTree(Element* root);
};
this is so simple you need to use NOTIFY
define your properties like this :
Q_PROPERTY (QString name READ name WRITE setName NOTIFY nameChanged)
then you need to define each one like this :
public : QString name() const;
signals :
void nameChanged(QString name);
public slots:
void setName(const QString &name);
private QString _name;
and then you should define body in cpp like this :
QString className::name() const{
return _name;
}
void className::setName(const QString &name){
if(name==_name) return;
_name = name;
Q_EMIT nameChanged(_name);
}
after registering it to QML with qmlRegisterType<ClassName>("com.className",1,0,"className");
simply set name it will notify if it changes for example in a textfield set text to that name property

Reading a QList<QPoint> in QML

I have the following class definition:
class PlaneGridQML : public QObject
{
Q_OBJECT
........
Q_INVOKABLE QList<QPoint> getPlanesPoints() {
return m_PlaneGrid.getPlanesPoints();
......
}
In main I am making it available to the QML engine
PlaneGridQML pgq(10, 10, 3, false);
engine.rootContext()->setContextProperty("PlaneGrid",&pgq);
Then inside a QML Component I would like to do the following:
Connections {
target: PlaneGrid
onPlanesPointsChanged: {
var pointsArray = PlaneGrid.getPlanesPoints()
console.log(pointsArray[0].x)
}
}
I am getting an error "Unknown method return type: QList" and obviously I cannot read pointsArray and display its first member. According to the Qt documentation QPoint can be directly transferred as a JS type. On the other hand QPoint does not derive from QObject.
Can anyone explain how can I list the elements of the array ( the QPoints ) in the JS function?
Yes, a QPoint will be auto converted to QML, and even a QList of certain objects will get auto converted to QML, but just because QList<int> works and QPoint work, it is not necessary mean that QList<QPoint> will also work.
This is the list of automatic types in a list that QML supports:
QList<int>
QList<qreal>
QList<bool>
QList<QString> and QStringList
QList<QUrl>
QVector<int>
QVector<qreal>
QVector<bool>
As you see, QPoint is not one of them.
Wrapping in a QObject would be a waste.
But do not despair. I suggest you declare QList<QPoint> * as a meta type, then you can wrap that in a QVariant to pass it as a opaque pointer to QML. Then, in your PlaneGridQML or another, single QObject derived type, you implement an interface to get the size of the list and individual points at a given index.
You basically implement a standalone accessor / manipulator for lists of points. And you are all set.
I also strongly recommend that you use QVector instead of QList in this scenario.
class PlaneGridQML : public QObject
{
Q_OBJECT
........
Q_INVOKABLE QPoint get(int i) { return planesPoints[i]; }
...
}
...
console.log(PlaneGrid.get(0).x)
If you only have one plane grid and one set of points, you don't even need to pass the opaque pointer to QML, you can just directly use the plane grid to get the size and individual elements via index. Easy peasy.
You can expose QPoint to QML, but not QList<QPoint>.
I see three possible solutions to your problem:
Add arguments to PlaneGridQML::planesPointsChanged signal. You cannot expose QList<QPoint> but you can send two variables of type QList<qreal> as the signal arguments. One list could hold x values and another y values.
Implement int PlaneGridQML::getPlanesPointsCount() and QPoint PlaneGridQML::getPlanesPoint(int index). So you have two functions - one which returns how many points there are, and second which return point for each index.
Create a new class that will hold two variables (planePointX, planePointY) and return QQmlListProperty<NewClass> with PlaneGridQML::getPlanesPoints. However this approach is rather not a very good idea for your case (too complicated). More info.

I am not sure how to create a getter and setter for data member like: QMap<QString, QVariant> content[2]

first of all I'd rather create the array dynamically. Need instructions, since I don't want to guess how many key/value pairs I'm getting from JSON Document.
secondly, I need to know how to write the getter and setter for a class like this.
I'll appreciate a detail explanation please.
// this is a sample class
class A : QOBject
{
Q_OBJECT
private:
QMap<QString, QVariant> content[2]
};