概述
QAbstractListModel 是 Qt 框架中为列表视图提供数据的抽象基类,它是 QAbstractItemModel 的简化版本,专门用于一维列表数据。
QObject
└── QAbstractItemModel
└── QAbstractListModel
核心特性
1. 简化的一维数据模型
-
专门为列表数据设计(ListView、Repeater等)
-
不需要处理复杂的行列和父子关系
-
相比
QAbstractItemModel更易于实现
2. 自动索引管理
-
自动为每个数据项创建
QModelIndex -
索引的 row 对应列表位置,column 始终为 0
-
parent 索引始终为无效索引
🎯 必须重写的纯虚函数
1. rowCount()
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
作用 : 返回模型中的数据行数
参数 : parent - 对于列表模型,此参数应始终为无效索引
返回: 数据项的总数
示例:
int StudentModel::rowCount(const QModelIndex &parent) const
{
// 列表模型中parent应该无效
if (parent.isValid())
return 0;
return m_students.size();
}
2. data()
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
作用 : 返回指定索引和角色的数据
参数:
-
index: 数据项索引(row, column) -
role: 数据角色(显示、装饰、工具提示等)
返回: 包含数据的 QVariant
示例:
QVariant StudentModel::data(const QModelIndex &index, int role) const
{
// 检查索引有效性
if (!index.isValid() || index.row() < 0 || index.row() >= m_students.size())
return QVariant();
const Student &student = m_students.at(index.row());
switch (role) {
case Qt::DisplayRole:
case NameRole:
return student.name;
case AgeRole:
return student.age;
case ScoreRole:
return student.score;
// ... 其他角色
default:
return QVariant();
}
}
3. roleNames()
QHash<int, QByteArray> roleNames() const override;
作用 : 定义角色ID与角色名称的映射关系
返回: 角色ID到角色名称的哈希表
示例:
QHash<int, QByteArray> StudentModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[AgeRole] = "age";
roles[ScoreRole] = "score";
roles[ColorRole] = "color";
return roles;
}
数据变更通知机制
1. 数据插入
void insertData(int position, int count)
{
beginInsertRows(QModelIndex(), position, position + count - 1);
// ... 实际插入数据操作
endInsertRows();
}
2. 数据删除
void removeData(int position, int count)
{
beginRemoveRows(QModelIndex(), position, position + count - 1);
// ... 实际删除数据操作
endRemoveRows();
}
3. 数据修改
void updateData(int position)
{
QModelIndex topLeft = createIndex(position, 0);
QModelIndex bottomRight = createIndex(position, 0);
emit dataChanged(topLeft, bottomRight, {ScoreRole, ColorRole});
}
4. 模型重置
void resetModel()
{
beginResetModel();
// ... 完全重置数据
endResetModel();
}
📊 内置数据角色
Qt 提供了一些标准角色:
| 角色 | 值 | 描述 |
|---|---|---|
Qt::DisplayRole |
0 | 主要显示数据 |
Qt::DecorationRole |
1 | 图标等装饰数据 |
Qt::EditRole |
2 | 可编辑的数据 |
Qt::ToolTipRole |
3 | 工具提示文本 |
Qt::StatusTipRole |
4 | 状态栏提示 |
Qt::WhatsThisRole |
5 | "这是什么"帮助 |
Qt::SizeHintRole |
13 | 项的大小提示 |
🛠️ 完整实现示例
基础列表模型
class StringListModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit StringListModel(const QStringList &strings, QObject *parent = nullptr)
: QAbstractListModel(parent), m_strings(strings) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
if (parent.isValid())
return 0;
return m_strings.size();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (!index.isValid() || index.row() >= m_strings.size())
return QVariant();
if (role == Qt::DisplayRole || role == Qt::EditRole)
return m_strings.at(index.row());
return QVariant();
}
QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> roles;
roles[Qt::DisplayRole] = "display";
return roles;
}
private:
QStringList m_strings;
};
高级自定义模型
class AdvancedListModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum CustomRoles {
TitleRole = Qt::UserRole + 1,
DescriptionRole,
IconRole,
ProgressRole,
StatusRole
};
explicit AdvancedListModel(QObject *parent = nullptr) : QAbstractListModel(parent) {}
// 必须实现的方法
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent)
return m_items.size();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size())
return QVariant();
const DataItem &item = m_items.at(index.row());
switch (role) {
case TitleRole:
case Qt::DisplayRole:
return item.title;
case DescriptionRole:
return item.description;
case IconRole:
return item.icon;
case ProgressRole:
return item.progress;
case StatusRole:
return item.status;
case Qt::ToolTipRole:
return QString("%1\n%2").arg(item.title).arg(item.description);
default:
return QVariant();
}
}
QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> roles;
roles[TitleRole] = "title";
roles[DescriptionRole] = "description";
roles[IconRole] = "icon";
roles[ProgressRole] = "progress";
roles[StatusRole] = "status";
return roles;
}
// 自定义方法
Q_INVOKABLE void addItem(const QString &title, const QString &description)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_items.append(DataItem{title, description, "default.png", 0, "active"});
endInsertRows();
emit countChanged();
}
Q_INVOKABLE void removeItem(int index)
{
if (index < 0 || index >= m_items.size())
return;
beginRemoveRows(QModelIndex(), index, index);
m_items.removeAt(index);
endRemoveRows();
emit countChanged();
}
Q_INVOKABLE void updateProgress(int index, int progress)
{
if (index < 0 || index >= m_items.size())
return;
m_items[index].progress = progress;
QModelIndex modelIndex = createIndex(index, 0);
emit dataChanged(modelIndex, modelIndex, {ProgressRole});
}
signals:
void countChanged();
private:
struct DataItem {
QString title;
QString description;
QString icon;
int progress;
QString status;
};
QList<DataItem> m_items;
};
🔗 QML 中的使用
基本使用
ListView {
model: advancedModel
delegate: Rectangle {
width: parent.width
height: 60
Row {
spacing: 10
Image {
source: model.icon
width: 40; height: 40
}
Column {
Text { text: model.title; font.bold: true }
Text { text: model.description; color: "gray" }
}
ProgressBar {
value: model.progress
width: 100
}
}
}
}
角色访问方式
// 方式1: 通过角色名直接访问
text: model.title
// 方式2: 通过 modelData 访问(当只有 DisplayRole 时)
text: model.modelData
// 方式3: 通过索引访问
text: model.display
⚡ 性能优化技巧
1. 批量操作
// 不好的做法:逐个添加
for (const auto &item : items) {
beginInsertRows(...);
// 添加一个
endInsertRows();
}
// 好的做法:批量添加
beginInsertRows(QModelIndex(), rowCount(), rowCount() + items.size() - 1);
m_items.append(items);
endInsertRows();
2. 精确的数据变更通知
// 只通知实际变化的角色
emit dataChanged(index, index, {SpecificRole});
// 而不是通知所有角色
emit dataChanged(index, index);
3. 使用正确的父索引
// 列表模型始终使用无效的父索引
beginInsertRows(QModelIndex(), ...);
beginRemoveRows(QModelIndex(), ...);
🎨 高级特性
1. 可编辑模型
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
{
if (!index.isValid() || role != Qt::EditRole)
return false;
m_data[index.row()] = value.toString();
emit dataChanged(index, index, {role});
return true;
}
Qt::ItemFlags flags(const QModelIndex &index) const override
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable | QAbstractListModel::flags(index);
}
2. 拖放支持
Qt::DropActions supportedDropActions() const override
{
return Qt::MoveAction;
}
QMimeData *mimeData(const QModelIndexList &indexes) const override
{
// 实现 MIME 数据创建
}
bool dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent) override
{
// 实现拖放数据处理
}
🔍 调试技巧
1. 角色名称调试
void printRoleNames()
{
auto roles = roleNames();
for (auto it = roles.begin(); it != roles.end(); ++it) {
qDebug() << "Role:" << it.key() << "Name:" << it.value();
}
}
2. 数据验证
bool isIndexValid(const QModelIndex &index) const
{
return index.isValid() &&
index.row() >= 0 &&
index.row() < rowCount() &&
index.model() == this;
}
QAbstractListModel 提供了强大而灵活的方式来管理列表数据,通过与 Qt 的视图组件(特别是 QML ListView)紧密集成,可以创建高性能、响应式的用户界面。