一、核心原理
Qt中自定义列表模型,核心是继承 QAbstractListModel(单列表)或 QAbstractTableModel(表格列表),重写其纯虚函数,实现数据的存储、访问、更新等逻辑。
QAbstractListModel 是所有列表模型的抽象基类,它定义了模型与视图(如QListView、QListWidget)交互的标准接口,我们只需重写关键虚函数,就能实现自定义的数据逻辑,无需关心视图的渲染细节,符合Qt的"模型-视图-控制器(MVC)"设计模式。
核心逻辑:模型负责管理数据(如存储自定义结构体、数组等),视图通过模型提供的接口读取/修改数据,当数据发生变化时,模型通过信号通知视图更新,实现数据与视图的解耦。
二、必备重写函数(核心)
继承QAbstractListModel后,必须重写以下4个纯虚函数,否则无法实例化模型:
-
rowCount(const QModelIndex &parent = QModelIndex()) const:返回列表的行数(数据总数),parent默认为空(列表模型无父子关系)。 -
data(const QModelIndex &index, int role = Qt::DisplayRole) const:根据索引(index)和角色(role)返回对应的数据,是模型与视图交互的核心接口。 -
setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole):根据索引和角色修改数据,修改成功后需发送dataChanged信号通知视图。 -
flags(const QModelIndex &index) const:返回索引对应的item的状态(如是否可编辑、可选中、可勾选等)。
可选重写函数(根据需求添加):
-
headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const:设置列表的表头(如QListView的水平/垂直表头)。 -
insertRows(int row, int count, const QModelIndex &parent = QModelIndex()):插入行(添加数据),需先调用beginInsertRows,后调用endInsertRows,避免视图错乱。 -
removeRows(int row, int count, const QModelIndex &parent = QModelIndex()):删除行(移除数据),需先调用beginRemoveRows,后调用endRemoveRows。
三、完整实现步骤(示例:自定义用户信息列表)
步骤1:定义自定义数据结构体(存储列表项数据)
首先定义一个结构体,存储每一行(每一个列表项)的数据,示例为用户信息(姓名、年龄、性别):
cpp
#include <QAbstractListModel>
#include <QString>
#include <QList>
// 自定义数据结构体
struct UserInfo {
QString name; // 姓名
int age; // 年龄
QString gender; // 性别
};
// 声明自定义列表模型
class CustomListModel : public QAbstractListModel {
Q_OBJECT
public:
// 构造函数,传入父对象
explicit CustomListModel(QObject *parent = nullptr);
步骤2:重写纯虚函数(核心实现)
在CustomListModel类中,重写必备的4个纯虚函数,以及常用的可选函数,实现数据的管理与交互:
cpp
private:
QList<UserInfo> m_userList; // 存储所有用户数据(模型的核心数据容器)
public:
// 1. 返回行数(数据总数)
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
Q_UNUSED(parent); // 列表模型无父子关系,忽略parent
return m_userList.size();
}
// 2. 返回指定索引、指定角色的数据
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
// 校验索引有效性(行号不能超出范围)
if (!index.isValid() || index.row() < 0 || index.row() >= m_userList.size()) {
return QVariant();
}
// 获取当前行的用户数据
const UserInfo &user = m_userList.at(index.row());
// 根据角色返回对应数据(Qt内置角色 + 自定义角色)
switch (role) {
case Qt::DisplayRole: // 视图默认显示的文本(可自定义格式)
return QString("%1(%2岁,%3)").arg(user.name).arg(user.age).arg(user.gender);
case Qt::EditRole: // 编辑模式下的原始数据(可选,用于编辑时获取完整信息)
return QVariant::fromValue(user);
// 自定义角色(用于获取单个字段,如单独获取姓名、年龄)
case 1001: // 自定义角色:姓名
return user.name;
case 1002: // 自定义角色:年龄
return user.age;
case 1003: // 自定义角色:性别
return user.gender;
default:
return QVariant();
}
}
// 3. 修改指定索引、指定角色的数据
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override {
if (!index.isValid() || index.row() < 0 || index.row() >= m_userList.size()) {
return false; // 索引无效,修改失败
}
UserInfo &user = m_userList[index.row()]; // 获取当前行的可修改引用
bool changed = false;
// 根据角色修改对应数据
switch (role) {
case Qt::EditRole: // 编辑模式下,直接设置完整的UserInfo对象
if (value.canConvert<UserInfo>()) {
user = value.value<UserInfo>();
changed = true;
}
break;
case 1001:
user.name = value.toString();
changed = true;
break;
case 1002:
user.age = value.toInt();
changed = true;
break;
case 1003:
user.gender = value.toString();
changed = true;
break;
default:
break;
}
// 若数据修改成功,发送信号通知视图更新该索引对应的内容
if (changed) {
emit dataChanged(index, index); // 两个index相同,表示只更新当前行
return true;
}
return false;
}
// 4. 返回item的状态(可编辑、可选中)
Qt::ItemFlags flags(const QModelIndex &index) const override {
if (!index.isValid()) {
return Qt::NoItemFlags;
}
// 可选中、可编辑、可启用(默认常用配置)
return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
}
// 可选:插入行(添加数据)
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
Q_UNUSED(parent);
// 校验插入位置(row不能小于0,不能大于当前行数)
if (row < 0 || row > rowCount()) {
return false;
}
beginInsertRows(parent, row, row + count - 1); // 通知视图:开始插入行
// 插入count个空的UserInfo对象(可根据需求初始化)
for (int i = 0; i < count; ++i) {
m_userList.insert(row + i, UserInfo());
}
endInsertRows(); // 通知视图:插入完成
return true;
}
// 可选:删除行(移除数据)
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
Q_UNUSED(parent);
if (row < 0 || row + count > rowCount()) {
return false;
}
beginRemoveRows(parent, row, row + count - 1); // 通知视图:开始删除行
for (int i = 0; i < count; ++i) {
m_userList.removeAt(row);
}
endRemoveRows(); // 通知视图:删除完成
return true;
}
// 可选:添加单个用户数据(对外提供接口,方便外部调用)
void addUser(const UserInfo &user) {
insertRows(rowCount(), 1); // 在最后一行插入1行
QModelIndex index = createIndex(rowCount() - 1, 0); // 创建最后一行的索引
setData(index, QVariant::fromValue(user), Qt::EditRole); // 设置数据
}
// 可选:获取单个用户数据(对外提供接口)
UserInfo getUser(int row) const {
if (row < 0 || row >= m_userList.size()) {
return UserInfo();
}
return m_userList.at(row);
}
};
步骤3:实现构造函数(初始化数据)
在.cpp文件中实现构造函数,可初始化一些测试数据,方便调试:
cpp
#include "CustomListModel.h"
CustomListModel::CustomListModel(QObject *parent) : QAbstractListModel(parent) {
// 初始化测试数据(可选)
m_userList << UserInfo{"张三", 25, "男"}
<< UserInfo{"李四", 28, "女"}
<< UserInfo{"王五", 30, "男"};
}
步骤4:模型与视图绑定(使用自定义模型)
在UI界面中,使用QListView(或QListWidget)绑定自定义模型,实现数据的显示与交互,示例在MainWindow中使用:
cpp
#include "MainWindow.h"
#include "CustomListModel.h"
#include <QListView>
#include <QVBoxLayout>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
// 1. 创建自定义模型
CustomListModel *model = new CustomListModel(this);
// 2. 创建视图(QListView)
QListView *listView = new QListView(this);
// 3. 绑定模型与视图
listView->setModel(model);
// 4. 可选:设置视图为可编辑(双击item可编辑)
listView->setEditTriggers(QAbstractItemView::DoubleClicked);
// 5. 布局设置
QWidget *centralWidget = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(centralWidget);
layout->addWidget(listView);
setCentralWidget(centralWidget);
// 示例:添加新数据(对外接口调用)
model->addUser(UserInfo{"赵六", 22, "女"});
// 示例:修改数据(通过索引修改)
QModelIndex index = model->createIndex(0, 0); // 第0行索引
model->setData(index, "张三(修改后)", 1001); // 修改姓名(自定义角色1001)
// 示例:删除数据(删除第1行)
model->removeRows(1, 1);
}
四、关键注意事项
-
索引有效性校验:所有操作(data、setData等)前,必须校验index是否有效(isValid())、行号是否在合理范围,否则会导致程序崩溃。
-
信号发送:数据修改、插入、删除后,必须发送对应的信号(dataChanged、beginInsertRows/endInsertRows等),否则视图不会更新。
-
角色使用:Qt内置角色(如DisplayRole、EditRole)用于视图默认交互,自定义角色用于获取/修改单个字段,角色值建议大于1000(避免与Qt内置角色冲突)。
-
数据容器:模型内部建议使用QList、QVector等容器存储数据,方便插入、删除、访问,避免使用普通数组(不易管理大小)。
-
线程安全:若模型数据在子线程中修改,需通过信号槽机制通知主线程的模型更新,避免跨线程直接操作模型数据(Qt视图仅支持主线程更新)。
五、扩展功能(可选)
-
自定义委托(QStyledItemDelegate):配合模型使用,实现列表项的自定义渲染(如设置颜色、字体、图标)、自定义编辑控件(如下拉框、日期选择器)。
-
排序功能:重写
sort(int column, Qt::SortOrder order)函数,实现数据排序(需结合数据容器的排序接口)。 -
过滤功能:结合QSortFilterProxyModel,实现列表数据的过滤(如按姓名、年龄筛选)。
六、常见问题排查
-
视图不显示数据:检查rowCount是否返回正确的行数、data函数是否返回有效QVariant、模型与视图是否正确绑定。
-
数据修改后视图不更新:检查setData函数是否发送dataChanged信号、修改后是否返回true。
-
插入/删除行后视图错乱:检查是否调用了beginInsertRows/endInsertRows、beginRemoveRows/endRemoveRows,且参数(row范围)正确。
-
编辑功能无效:检查flags函数是否返回Qt::ItemIsEditable,setData函数是否正确处理EditRole。