从 QML ListView 操作 QAbstractListModel 中的数据
Manipulate data in a QAbstractListModel from a QML ListView
我有一个 QML ListView,它使用 QAbstractListModel 子类作为模型。
ListView {
id: myListView
x: 208
y: 19
width: 110
height: 160
delegate: myListDelegate {}
model: MyListModel
opacity: 0
}
模型是 MyListItem
的列表。
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;
};
在委托中我有一个鼠标区域。
我如何拦截鼠标区域上的点击并从我的 QList 模型中选择 that MyListItem
并将其发送到应用程序的 C++ 部分内的某处?
评论提到从 data()
返回指向 MyListItem
的指针到 QML 并在 QML 中访问和修改它。这需要您的 MyListItem
继承自 QObject
并为您要在 QML 中访问的每个成员添加一个 Q_PROPERTY
。它还需要密切关注对象所有权 (QQmlEngine::ObjectOwnership
)。
还有一种方法:实现QAbstractListModel::setData()
和QAbstractListModel::roleNames()
,模型内容可以从QML改变,如model.roleName = foo
.
下面的最小工作示例,每次单击委托时数量都会翻倍:
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;
}
}
}
}
您也可以在委托中使用index
属性来操作数据。您只需要使用模型上的索引方法将 QML 索引转换为 QModelIndex
。这是一个简单的示例,每次单击列表项时,我们都会将显示值更改为字符串“3”。
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's 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);
}
}
}
}
除了代表中的 index
属性 之外,所有默认角色名称都可以在代表中使用。因此,例如,我之前使用 decoration
角色来设置我的 Rectangle
委托的 color
属性。有关更多信息,请参阅 this list。
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
}
}
另请参阅 this link,其中 Mitch Curtis 建议使用 qmlRegisterUncreatableType 来注册用户枚举。
因为 setRoleNames() 在 QAbstractListModel 中是绝对的。您可以覆盖 roleNames() 并显式添加您的角色。继承QAbstractListModel的简单实现写在下面
class BaseListModel : public QAbstractListModel
{
Q_OBJECT
Q_ENUMS(Roles)
public:
enum Roles {
Name = Qt::UserRole + 1
};
virtual QHash<int, QByteArray> roleNames() const;
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override;
private:
QStringList _list;
};
// class
BaseListModel::BaseListModel(QObject *parent) :
QAbstractListModel(parent)
{
QHash<int, QByteArray> h = RecipeListModel::roleNames();
}
QHash<int, QByteArray> BaseListModel::roleNames() const {
return {
{ Name, "name" },
};
}
int BaseListModel::rowCount(const QModelIndex &parent) const {
if (parent.isValid())
return 0;
return _list.size();
}
QVariant BaseListModel::data(const QModelIndex &index, int role) const {
if (!hasIndex(index.row(), index.column(), index.parent()))
return {};
return _list.at(index.row())->data(role);
}
bool RecipeListModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid())
return false;
bool ret = _list.at(index.row())->setData(role, value);
if (ret) {
emit dataChanged(index, index, { role });
}
return ret;
}
QVariant BaseListModel::data(int role) const {
switch(role) {
case Name:
return name();
default:
return QVariant();
}
}
bool BaseListModel::setData(int role, const QVariant &value)
switch(role) {
case Name:
setName(value.toString());
return true;
default:
return false;
}
}
我有一个 QML ListView,它使用 QAbstractListModel 子类作为模型。
ListView {
id: myListView
x: 208
y: 19
width: 110
height: 160
delegate: myListDelegate {}
model: MyListModel
opacity: 0
}
模型是 MyListItem
的列表。
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;
};
在委托中我有一个鼠标区域。
我如何拦截鼠标区域上的点击并从我的 QList 模型中选择 that MyListItem
并将其发送到应用程序的 C++ 部分内的某处?
评论提到从 data()
返回指向 MyListItem
的指针到 QML 并在 QML 中访问和修改它。这需要您的 MyListItem
继承自 QObject
并为您要在 QML 中访问的每个成员添加一个 Q_PROPERTY
。它还需要密切关注对象所有权 (QQmlEngine::ObjectOwnership
)。
还有一种方法:实现QAbstractListModel::setData()
和QAbstractListModel::roleNames()
,模型内容可以从QML改变,如model.roleName = foo
.
下面的最小工作示例,每次单击委托时数量都会翻倍:
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;
}
}
}
}
您也可以在委托中使用index
属性来操作数据。您只需要使用模型上的索引方法将 QML 索引转换为 QModelIndex
。这是一个简单的示例,每次单击列表项时,我们都会将显示值更改为字符串“3”。
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's 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);
}
}
}
}
除了代表中的 index
属性 之外,所有默认角色名称都可以在代表中使用。因此,例如,我之前使用 decoration
角色来设置我的 Rectangle
委托的 color
属性。有关更多信息,请参阅 this list。
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
}
}
另请参阅 this link,其中 Mitch Curtis 建议使用 qmlRegisterUncreatableType 来注册用户枚举。
因为 setRoleNames() 在 QAbstractListModel 中是绝对的。您可以覆盖 roleNames() 并显式添加您的角色。继承QAbstractListModel的简单实现写在下面
class BaseListModel : public QAbstractListModel
{
Q_OBJECT
Q_ENUMS(Roles)
public:
enum Roles {
Name = Qt::UserRole + 1
};
virtual QHash<int, QByteArray> roleNames() const;
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override;
private:
QStringList _list;
};
// class
BaseListModel::BaseListModel(QObject *parent) :
QAbstractListModel(parent)
{
QHash<int, QByteArray> h = RecipeListModel::roleNames();
}
QHash<int, QByteArray> BaseListModel::roleNames() const {
return {
{ Name, "name" },
};
}
int BaseListModel::rowCount(const QModelIndex &parent) const {
if (parent.isValid())
return 0;
return _list.size();
}
QVariant BaseListModel::data(const QModelIndex &index, int role) const {
if (!hasIndex(index.row(), index.column(), index.parent()))
return {};
return _list.at(index.row())->data(role);
}
bool RecipeListModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid())
return false;
bool ret = _list.at(index.row())->setData(role, value);
if (ret) {
emit dataChanged(index, index, { role });
}
return ret;
}
QVariant BaseListModel::data(int role) const {
switch(role) {
case Name:
return name();
default:
return QVariant();
}
}
bool BaseListModel::setData(int role, const QVariant &value)
switch(role) {
case Name:
setName(value.toString());
return true;
default:
return false;
}
}