Manipulating data in QAbstractListModel from QML ListView
I have a QML ListView that uses a QAbstractListModel subclass as a model.
ListView {
id: myListView
x: 208
y: 19
width: 110
height: 160
delegate: myListDelegate {}
model: MyListModel
opacity: 0
}
The model is a list of MyListItem
s.
class MyListModel : public QAbstractListModel
{
Q_OBJECT
public:
enum MyRoles {
HeadingRole = Qt::UserRole + 1,
DescriptionRole,
QuantityRole
};
explicit MyListModel(QObject *parent = 0);
void addMyListItem(const MyListItem &item);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
void dropList();
private:
QList<MyListItem> m_list;
};
In a delegate, I have a mousearea.
How can I intercept the mouse click and select which one MyListItem
from my QList model and send it somewhere inside the C ++ part of the application?
source to share
The comments mention returning a pointer to MyListItem
from data()
in QML and accessing and modifying it in QML. This requires yours to MyListItem
inherit from QObject
and add one Q_PROPERTY
for each member you want to access in QML. It also requires close attention to object ownership ( QQmlEngine::ObjectOwnership
).
There is another way: Embed QAbstractListModel::setData()
and QAbstractListModel::roleNames()
, and the content of the model can be changed from QML, for example model.roleName = foo
.
A minimal working example below that doubles the count on each click of the delegate:
C ++:
struct MyListItem
{
QString heading;
QString description;
int quantity;
};
class MyListModel : public QAbstractListModel
{
Q_OBJECT
Q_ENUMS(MyRoles)
public:
enum MyRoles {
HeadingRole = Qt::UserRole + 1,
DescriptionRole,
QuantityRole
};
using QAbstractListModel::QAbstractListModel;
QHash<int,QByteArray> roleNames() const override {
return { { HeadingRole, "heading" },
{ DescriptionRole, "description" },
{ QuantityRole, "quantity" },
};
}
int rowCount(const QModelIndex & parent = QModelIndex()) const override {
if (parent.isValid())
return 0;
return m_list.size();
}
bool setData(const QModelIndex &index, const QVariant &value, int role) override
{
if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid())
return false;
MyListItem &item = m_list[index.row()];
if (role == DescriptionRole) item.description = value.toString();
else if (role == HeadingRole) item.heading = value.toString();
else if (role == QuantityRole) item.quantity = value.toInt();
else return false;
emit dataChanged(index, index, { role } );
return true ;
}
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override {
if (!hasIndex(index.row(), index.column(), index.parent()))
return {};
const MyListItem &item = m_list.at(index.row());
if (role == DescriptionRole) return item.description;
if (role == HeadingRole) return item.heading;
if (role == QuantityRole) return item.quantity;
return {};
}
private:
QVector<MyListItem> m_list = {
{ "heading 1", "description 1", 1 },
{ "heading 2", "description 2", 42 },
{ "heading 3", "description 3", 4711 }
};
};
QML:
ListView {
id: listView
anchors.fill: parent
model: MyListModel {}
delegate: Item {
implicitHeight: text.height
width: listView.width
Text {
id: text
text: model.heading + " " + model.description + " " + model.quantity
}
MouseArea {
anchors.fill: text
onClicked: {
model.quantity *= 2;
}
}
}
}
source to share
You can also use a property index
in a Deletet to manipulate data. You just need to convert the QML index to QModelIndex
using the index method on your model. Here's a simple example where we change the displayed value to the string "3" every time we click a list item.
ListView {
id: listView
anchors.fill: parent
model: my_model
delegate: Rectangle {
height: 50
width: listView.width
MouseArea {
anchors.fill: parent
onClicked: {
// Column is always zero as it a list
var column_number = 0;
// get `QModelIndex`
var q_model_index = my_model.index(index, column_number);
// see for list of roles:
// http://doc.qt.io/qt-5/qabstractitemmodel.html#roleNames
var role = 1
var data_changed = my_model.setData(q_model_index, "3", role);
console.log("data change successful?", data_changed);
}
}
}
}
In addition to the property index
in delegates, all default role names are available in delegates. So, for example, I used a role decoration
to set a property for color
my delegate Rectangle
before. See this list for details .
ListView {
delegate: Rectangle {
// list items have access to all default `roleNames`
// in addition to the `index` property.
// For example, using the decoration role, demo'd below
color: decoration
}
}
Also see this link where Mitch Curtis recommends using qmlRegisterUncreatableType to register user enumerations.
source to share