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.
Related
This is my Wavefrontrenderer class.
Class WavefrontRenderer : public QMainWindow , private
Ui::WavefrontRendererClass
{
Q_OBJECT
//Q_PROPERTY( QColor m_color READ m_color NOTIFY colorChanged)
public:
WavefrontRenderer(TreeModel* model , QWidget *parent = Q_NULLPTR);
void iterate(const QModelIndex & index, const QAbstractItemModel * model);
void render();
void RenderTreeElement(QModelIndex index);
private:
//Ui::WavefrontRendererClass ui;
TextureManager textureManager;
TextureManagerCubeMap textureManagerCubeMap;
QColor m_color;
void FillComboBox();
private slots:
void PositionXYZ();
};
/////////////////////////////////////////////////////////////////////////////////
WavefrontRenderer::WavefrontRenderer(TreeModel* model , QWidget *parent) :
QMainWindow(parent)
{
setupUi(this);
treeView->setModel(model);
treeView->setDragEnabled(true);
treeView->setAcceptDrops(true);
treeView->installEventFilter(this);
connect(doubleSpinBoxPositionX, SIGNAL(valueChanged(double)), this ,
SLOT(PositionXYZ()));
}
/////////////////////////////////////////////////////////////////////////////////
I am creating controls at runtime from another class to which i pass the Wavefrontrenderer class as a pointer.
void Container::CreateUI(QHBoxLayout* layout)
{
wavefrontrenderer // Pointer defined as a private member
QDoubleSpinBox *PositionXSpinBox = new QDoubleSpinBox;
PositionXSpinBox->setRange(-10000, 10000);
PositionXSpinBox->setSingleStep(.1);
PositionXSpinBox->setValue(x);
layout->addWidget(PositionXSpinBox);
bool ok = QObject::connect(PositionXSpinBox,
SIGNAL(valueChanged(double)), wavefrontrenderer , SLOT(print()));
qDebug() << "The slot is connected =" << ok;
}
I have defined print as a public slot member in Container class.
The issue is that the connect tries to locate print() function in WavefrontRenderer class rather than Container class.
How can i make connect call the print() funtion of the Container class.
I have got a custom structure defined in C++, which contains several variables among them is a QList variable. I can now add a new structure dynamically in Qml listview, but my question is I also want to add a new item into QList inside the structure, I can do this in the background but fail to update the listview.
I think the error appears in the connect() function in C++. As I understand in order to add a new row need to call beginInsertRows() and endInsertRows(), but should be sender and receiver be the same in these two scenarios?
So I have got the following structure in C++ along with some signals and functions used to insert new rows.
"todolist.h":
struct ToDoItem
{
bool done;
QString description;
QList<int> list;
ToDoItem(){
done = false;
description = "text";
QList<int> mylist;
mylist.append(1);
list = mylist;
}
};
signals:
void preItemAppended();
void postItemAppended();
void preListAppended();
void postListAppended();
public slots:
void appendItem();
void appendList();
void ToDoList::appendItem()
{
emit preItemAppended();
mItems.append(ToDoItem());
emit postItemAppended();
}
void ToDoList::appendList()
{
emit preListAppended();
mItems[0].list.append(1);
emit postListAppended();
}
"todomodel.h"
void ToDoModel::setList(ToDoList *list)
{
beginResetModel();
if (mList)
mList->disconnect(this);
mList = list;
if (mList) {
connect(mList, &ToDoList::preItemAppended, this, [=]() {
const int index = mList->items().size();
beginInsertRows(QModelIndex(), index, index);
});
connect(mList, &ToDoList::postItemAppended, this, [=]() {
endInsertRows();
});
connect(mList, &ToDoList::preListAppended, this, [=]() {
const int index = mList->lists().size();
beginInsertRows(QModelIndex(), index, index);
});
connect(mList, &ToDoList::postListAppended, this, [=]() {
endInsertRows();
});
}
endResetModel();
}
The first two connect function works fine which are used to insert a new item but the second two connect functions fail.
It's not a good idea to connect signals to beginInsertRows and endInsertRows.
You should abstract that and rather connect to add or remove methods.
Assume you have your model class which implements QAbstractListModel:
class MyListModel : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(int size READ size NOTIFY sizeChanged)
public:
explicit myListModel(QObject *parent = nullptr);
~myListModel() override;
int rowCount(const QModelIndex &p) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
int size() const;
public slots:
void add(QObject *o);
QObject *remove(int i);
signals:
void sizeChanged();
private:
QList<QObject *> m_data;
};
The implementation of add and remove wouldbe as follows:
void MyListModel::add(QObject* o) {
int i = m_data.size();
beginInsertRows(QModelIndex(), i, i);
m_data.append(o);
o->setParent(this);
sizeChanged();
endInsertRows();
}
QObject * MyListModel::take(int i) {
if ((i > -1) && (i < m_data.size())) {
beginRemoveRows(QModelIndex(), i, i);
QObject * o = m_data.takeAt(i);
o->setParent(nullptr);
sizeChanged();
endRemoveRows();
return o;
}
return nullptr;
}
In this case, you can call add on newly added items, or if you're repopulating your list, you can clear it and loop over your list and add them 1 by 1.
ADDING CUSTOM OBJECT EXAMPLE:
class ToDoItem : public QObject {
Q_OBJECT
Q_PROPERTY(bool done READ done WRITE setDone NOTIFY doneChanged)
public:
explicit ToDoItem(QObject* parent = nullptr);
~ToDoItem() override;
bool done() const { return m_done_; }
void setDone(bool done) { m_done_ = done; emit doneChanged(done); }
signals:
void doneChanged(bool);
private:
bool m_done_;
};
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?
I'm working on a simple project to try and learn QT 5.7, QML, and C++. I want to create a simple interface that has a list of items that I can add and remove items from using a couple of buttons. I've been reading a bunch of different guides online trying to piece together something but I keep getting stuck. I've tried using QQmlListProperty<T> and QAbstractListModel but I have questions about both approaches:
For my project is QQmlListProperty<T> the right thing to use or I should use QAbstractListModel?
For either case how do I notify my QML view that the list has changed?
If I use 'QAbstractListModel', do I just need to create Q_INVOKABLE methods for adding and removing items from my list?
Below is my code for both QQmlListProperty<T> and QAbstractListModel so far. I've left out most of the implementation of the classes to keep this post short but if the implementation is needed I'm happy to add it.
QQmlListProperty Item Class
class PlaylistItemModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
public:
explicit PlaylistItemModel(QObject *parent = 0);
QString getName();
void setName(const QString &name);
signals:
void nameChanged();
public slots:
private:
QString _name;
};
QQmlListProperty List Class
class PlaylistModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<PlaylistItemModel> items READ getItems NOTIFY itemsChanged)
public:
explicit PlaylistModel(QObject *parent = 0);
QQmlListProperty<PlaylistItemModel> getItems() const;
Q_INVOKABLE void addItem(PlaylistItemModel *item);
Q_INVOKABLE void removeItem(PlaylistItemModel *item);
Q_INVOKABLE void clearItems();
static void append(QQmlListProperty<PlaylistItemModel> *list, PlaylistItemModel *item);
static PlaylistItemModel* at(QQmlListProperty<PlaylistItemModel> *list, int index);
static int count(QQmlListProperty<PlaylistItemModel> *list);
static void clear(QQmlListProperty<PlaylistItemModel> *list);
signals:
void itemsChanged();
public slots:
private:
QList<PlaylistItemModel*> _items;
};
Implementation of getItms():
QQmlListProperty<PlaylistItemModel> PlaylistModel::getItems() const
{
return QQmlListProperty<PlaylistItemModel>(this, _items, &append, &count, &at, &clear);
}
QAbstractListModel
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
enum ModelRoles
{
ItemRole = Qt::UserRole + 1
};
MyModel(QObject *parent = 0);
// QAbstractItemModel interface
int rowCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
QHash<int, QByteArray> roleNames() const;
private:
QList<QString> _listData;
QString itemAt(const QModelIndex &index) const;
};
I would generally suggest that QAbstractListModel is the right class to go with most of the time, unless you are sure you'll only be working with a simple list.
For either case how do I notify my QML view that the list has changed?
QAbstractItemModel (which QAbstractListModel inherits) has a number of different methods that you should call from your subclass to inform the view(s) connected to it that something has happened. When you're inserting items to it, you want QAbstractItemModel::beginInsertRows and QAbstractItemModel::endInsertRows.
If your model is representing something simple, like a list of names for instance, your insertion might look something like this:
Assuming:
class MyModel : public QAbstractListModel
{
public:
Q_INVOKABLE void addPerson(const QString &name);
private:
QVector<QString> m_names;
};
void MyModel::addPerson(const QString &name)
{
beginInsertRows(QModelIndex(), m_names.count(), m_names.count());
m_names.append(name);
endInsertRows();
}
You then need to implement QAbstractItemModel::rowCount, QAbstractItemModel::roleNames, and QAbstractItemModel::data at a minimum, but it looks like you've already got that handled. If you want to implement editing of data, you also want to implement QAbstractListModel::setData.
Once you've done that, register the type (using qmlRegisterType):
qmlRegisterType<MyModel>("MyModelImport", 1, 0, "MyModel");
And then import & instantiate it from QML, and use it on your view:
import MyModelImport 1.0
import QtQuick 2.6
ListView {
id: listView
anchors.fill: parent
model: MyModel {
}
delegate: TextInput {
text: model.text
onEditingFinished: {
// when the text is edited, save it back to the model (which will invoke the saveData we overloaded)
model.text = text
}
}
Rectangle {
height: 100
width: 400
color: "red"
Text {
text: "Add item"
}
MouseArea {
anchors.fill: parent
onClicked: listView.model.addName("Some new name")
}
}
}
I've got a hashmap ReferenceMap in a Qt serial terminal program.
The program receives a key to ReferenceMap in form of a reference followed by a value. It then uses the Qt hashmap insert function to insert the value. It's very neat and I would like to keep that part of the program.
The problem is that I would like to to do different things when different new values are coming in. I therefore would like to emit a signal when a value associated with a special place in the referencemap is changed.
According to the guide below, an object that emits a signal can easily be created.
http://qt-project.org/doc/qt-4.8/signalsandslots.html
I think I got a solution, however, and want to discuss wether it's a neat way or not.
I could create a class in like this
class Sfloat : public QObject
{
Q_OBJECT
public:
explicit Sfloat(QObject *parent = 0);
float Get();
void SetValue(float value);
private:
float MyFloat;
signals:
void ValueChanged(float newValue);
public slots:
};
The ReferenceMap could then be set to contain references to SFloat object like,
ReferenceMap(Key, &SFloat).
When I have the key, I could get the object reference, call SetValue() and a signal would be emitted. However, is this the best way to solve it?
I don't think that's a good idea. You are wrapping every float of your hash with a QObject which sounds very heavy to me.
You can hap the whole HashMap inside the QObject and emit the signal from there when the value you are interested in has changed.
class MyHashMap : public QObject
{
Q_OBJECT
public:
explicit MyHashMap(QObject *parent = 0);
float Get(const QString& key) const;
void SetValue(const QString& key, float value)
{
// only emit value changed if the value has actually changed
QHashMap<QString, float>::iterator it = myHashMap.find(key);
if(it != myHashMap.end()){
if(it.value != value){
myHashMap[key] = value;
emit ValueChanged(key, value);
}
}
}
private:
QHashMap<QString, float> myHashMap;
signals:
void ValueChanged(const QString& key, float newValue);
};
According to your comment, you want to update a label when a value has changed. Simply add all the labels to a QHashMap, then connect the ValueChanged signal to a slot that updates the right label:
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0)
{
...
...
myLabels["label1"] = myLabel1;
myLabels["label2"] = myLabel2;
QObject::connect(myLabels, SIGNAL(ValueChanged(const QString&, float)),
this, OnValueChanged(const QString&, float));
}
public slots:
void OnValueChanged(const QString& key, float newValue){
QHashMap<QString, QLabel*>::iterator it = myLabels.find(key);
if(it != myLabels.end()){
it.value()->setText(QString::number(newValue));
}
}
private:
QHashMap<QString, QLabel*> myLabels;
};