QML,How to dynamicaly change Item of Repeater from C++ - c++

I have QML Repeater on Grid, when i clicked on item i emit signal which processed by C++ class, and then changed array in C++ which then assign as model of QML Repeater. Is there a way to change just two elements of C++ model, not whole model as i did?
that's my qml file
Grid{
height:width
rows:8
columns: 8
Repeater{
id: chessPiecesRptr
...
signal chessfigureSelected(int index)
delegate: Item{
id:chessPiecesItm
Image{
...
}
MouseArea
{
anchors.fill:parent
onClicked: {
chessPiecesRptr.chessfigureSelected(index)
}
}
}
}
C++ method which update model of QML Repeater
void ChessFiguresClass::changeModel()
{
QStringList dataList;
for(int i=0;i<64;i++)
dataList.append(QChar(posArray[i]));
QQmlProperty::write(chessPiecesRptr, "model", QVariant::fromValue(dataList));
}

Contrary to the accepted answer it is indeed possible without going all the way to implement an entire QAbstractListModel.
It is true that QStringList doesn't have any way to notify for changes, but all you have to do is wrap it up in a property, something like this:
Q_PROPERTY(QVariant myModel READ myModel NOTIFY myModelChanged)
...
QVariant myModel() { return QVariant::fromValue(myStringList); }
...
void myModelChanged(); // signal
And just emit myModelChanged every time you want to reflect a change in the model, be that replacing it with a different model or changing it internally. And use the myModel property for the Repeater's model.
However, if you have a lot of items in the model or the delegates are complex, it is always a good idea to implement your own QAbstractListModel, because with the approach above, the repeater will recreate the entire model every time it "changes", whereas the QAbstractListModel will only update the actual changes.

I'm afraid it is not possible. QList (and QStringList) does not have inner mechanisms to notify Qml items about its changes. Only when model property from QML item is changed, the whole list is read again.
I had faced the same problem before and I implemented a string list using QAbstractListModel as base class. The header looks like this:
#ifndef _SIMPLEMODEL_H_
#define _SIMPLEMODEL_H_
#include <QtCore>
class SimpleStringModel : public QAbstractListModel
{
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_DISABLE_COPY(SimpleStringModel)
Q_OBJECT
public:
explicit SimpleStringModel(QObject* parent = 0);
SimpleStringModel(const QList<QString>& initList, QObject* parent = 0);
virtual ~SimpleStringModel();
private:
enum Roles{
ModelDataRole = Qt::UserRole+1
};
public:
int count() const;
public:
void append(const QString& item);
void insert(int index, const QString& item);
void append(const QList<QString>& items);
void insert(int index, const QList<QString>& items);
bool removeOne(const QString& item);
int removeAll(const QString& item);
void removeAt(int index);
QList<QString> list() const;
signals:
void countChanged();
// QAbstractItemModel interface
public:
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QHash<int, QByteArray> roleNames() const;
private:
QList<QString> m_data;
};
#endif //_SIMPLEMODEL_H_
You can get all the code here. I hope this help you.

Related

Qml listview add new row to a sub vector in the model

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_;
};

How to expose list of custom objects with Q_PROPERTY

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.

Adding and removing items from a C++ list in QML/QT 5.7

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")
}
}
}

How to get the old value when handling QAbstractItemModel::dataChanged() signal?

I have a QTableView which have set a QStandardItemModel. The user edits data in some index in the view and then the model emits the dataChanged() signal. In the SLOT where I am handling the SIGNAL I have the QModelIndex range of the user changes and thus I can get the new values the user have entered. How can I obtain the old values at that point?
After some research I figured out that there is no standard way to achive this behaviour. To solve the problem I had to inherit QStandardItemModel and reimplement setData() like this:
class RecallModel : public QStandardItemModel
{
public:
RecallModel (QObject * parent = 0) : QStandardItemModel(parent) {}
// Reimplemented
bool setData(const QModelIndex &index, const QVariant &value, int role= Qt::EditRole)
{
// backup the previous model data
if (role == Qt::EditRole || role == Qt::DisplayRole)
QStandardItemModel::setData(index, data(index), Qt::UserRole + 1);
return QStandardItemModel::setData(index, value, role);
}
};
And after that I can access the old data in the slot handling the dataChanged() signal:
void SomeObject::handleDataChange(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
...
const QVariant &vOldData = index.data(Qt::UserRole + 1); // here is the old data
const QVariant &vNewData = index.data(Qt::DisplayRole); // here is the new data
...
}
Since QStandardItemModel is a simple model, it does not have a signal with this feature. If you want such a feature you can subclass QAbstractItemModel and have your own custom class and implement setData and emit a custom signal which contains both the old and the new values.
As a workaround you can connect the itemChanged signal of QStandardItemModel to some slot :
connect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));
And store the new value as a Qt::UserRole in model to use it as the old value when the slot is called next time :
void MyClass::onModelChanged(QStandardItem *item)
{
disconnect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));
QVariant oldValue = item->data(Qt::UserRole);
item->setData(item->data(Qt::DisplayRole), Qt::UserRole); //Store the new value for next use
connect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));
}
User can change data with delegate so possible solution is:
#ifndef ITEMDELEGATE_H
#define ITEMDELEGATE_H
#include <QItemDelegate>
class ItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit ItemDelegate(QObject *parent = 0);
protected:
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void setEditorData(QWidget * editor, const QModelIndex & index) const;
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const;
void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const;
signals:
void dataChanged(QString oldValue,QString newValue) const;
public slots:
private:
mutable QString old;//we want change member data in const method
};
#endif // ITEMDELEGATE_H
As you can see many methods are const by default so I did some tricks (such as mutable) to avoid problems. Also in my edited answer I doesn't store old data in UserRole+1 , all done with old mutable variable.
cpp:
#include "itemdelegate.h"
#include <QLineEdit>
#include <QDebug>
ItemDelegate::ItemDelegate(QObject *parent) :
QItemDelegate(parent)
{
}
QWidget *ItemDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QLineEdit *editor = new QLineEdit(parent);
return editor;
}
void ItemDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
old = index.model()->data(index, Qt::EditRole).toString();//store old data
QLineEdit *line = qobject_cast<QLineEdit*>(editor);
line->setText(old);
}
void ItemDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index)const
{
QLineEdit *line = static_cast<QLineEdit*>(editor);
QString data = line->text();
emit dataChanged(old, line->text());
model->setData(index, data);
}
void ItemDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
Usage:
ItemDelegate * del = new ItemDelegate;
connect(del,&ItemDelegate::dataChanged,[=](QString oldValue,QString newValue) {
qDebug() << "old" << oldValue<< "new" <<newValue ;
});
ui->tableView->setItemDelegate(del);
I tested it and it works. It will work with different models. QTableView uses lineEdit as delegate by default, so user will not see any changes in view.
I used here C++11 (CONFIG += c++11 to .pro file) and new syntax of signals and slots, but of course you can use old syntax if you want.
All solutions before this answer rely on Qt-specific features. But you can move INSERT, UPDATE, DELETE (not SQL, but common) functionality outside Qt framework. Look at Unit Of Work Design Pattern, and related Domain Object pattern from "Patterns of Enterprise Application Architecture".
You can modify this patterns to hold old values and get it when you modify during setData().

Qt - reduce duplication between two QAbstractListModel subclasses

I've written two QAbstractListModel subclasses:
class Model1: public QAbstractListModel {
Q_OBJECT
public:
int rowCount(const QModelIndex& parent=QModelIndex()) const;
QVariant data(const QModelIndex& index, int role) const;
void clear();
private:
QVector<Obj1*> m_items;
};
and
class Model2: public QAbstractListModel {
Q_OBJECT
public:
int rowCount(const QModelIndex& parent=QModelIndex()) const;
QVariant data(const QModelIndex& index, int role) const;
void clear();
private:
QVector<Obj2*> m_items;
};
In addition, these two classes have a slot AddObj() which is dependent on the pointer used (Obj1* or Obj2*).
The matter is, constructors and destructors are the same, and so are rowCount and clear (they do the exact same thing). The only differences are in data and in what m_items is.
What's the best strategy to reduce duplication here? I tried with a base class:
class ModelBase: public QAbstractListModel {
Q_OBJECT
ModelBase(QObject* parent=0);
~ModelBase();
public:
virtual int rowCount(const QModelIndex& parent=QModelIndex()) const;
virtual QVariant data(const QModelIndex& index, int role) const = 0;
virtual void clear();
private:
QVector<QString*> m_items; // dummy vector
};
and then have the other classes derive from it:
class ModelDerived1: public ModelBase {
Q_OBJECT
public:
ModelDerived1(QObject* parent=0);
~ModelDerived1();
QVariant data(const QModelIndex& index, int role) const = 0;
private:
QVector<Obj1*> m_items;
};
The problem is that in such a case the derived class's data function is never called when this model is attached to a view.
Nevertheless, the derived class data() is not called (I put in debug statements, and they are never executed).
What am I doing wrong here?
EDIT: The solution is not what I posted here. In fact, it was completely off. The issue is in the rowCount function and in the private m_items QVector.
rowCount, if not present, calls the superclass method, which does not use the private m_items element from the derived class, but it uses the one from the superclass, which being dummy, returns always a size of 0, and thus data never gets called.