前言
Qt版本:6.8.0
QAbstractItemModel类为项目模型类提供了抽象接口,可以通过继承它并实现必要的函数即可自定义模型
如果要继承Qt的模型类那么需要实现那个模型类的所有纯虚函数,如果是QAbstractItemModel的子类只需要实现该子类里的纯虚函数即可,因为QAbstractItemModel的纯虚函数已经被该子类实现过了
下面的示例适用于表格和列表模型,树模型在单独的文章里写实现树模型
公共函数
对于所有使用index的参数的函数都可以通过使用index.isValid()来检测其有效性
构造函数与析构函数
QAbstractItemModel(QObject *parent = nullptr)
构造函数,创建一个抽象项模型,可指定父对象 parent(用于内存管理)。
virtual ~QAbstractItemModel()
虚析构函数,确保派生类正确释放资源。
核心纯虚函数
子类必须实现以下函数以定义模型结构
你如果自定义模型类继承自该类那么以下函数都必须实现否则为抽象类,对于Qt提供的部分类如QStringListModel等也实现了以下函数只是Qt官方隐式处理了
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const = 0
返回父索引 parent 下的列数。根节点用 QModelIndex() 表示。
- 列表模型
int columnCount(const QModelIndex &parent = QModelIndex()) const override {
// 列表模型不支持父子结构,若 parent 有效则返回 0
if (parent.isValid()) {
return 0;
}
return 1; // 根层级的列数固定为 1
}
- 表格模型
QVector<QVector<QString>> m_data = {
{"张三", "25"},
{"李四", "30"},
{"王五", "28"}
};
int columnCount(const QModelIndex &parent = QModelIndex()) const override {
// 表格模型不支持父子结构,若 parent 有效则返回 0
if (parent.isValid()) {
return 0;
}
return 2; //根据你的数据结构有几列就填几
}
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0
返回索引 index 对应角色 role(如 Qt::DisplayRole)的数据
注意:index.row()的有效范围为[0,rowCount()-1](列表模型)
- 角色类型
Qt::DisplayRole - 显示的文本
Qt::EditRole - 编辑时的文本
Qt::DecorationRole - 装饰(图标等)
Qt::ToolTipRole - 工具提示
Qt::StatusTipRole - 状态栏提示
Qt::WhatsThisRole - "这是什么"帮助文本
Qt::SizeHintRole - 项的大小提示
Qt::FontRole - 字体
Qt::TextAlignmentRole - 文本对齐方式
Qt::BackgroundRole - 背景色
Qt::ForegroundRole - 前景色(文字颜色)
Qt::CheckStateRole - 选中状态
Qt::InitialSortOrderRole - 初始排序顺序
Qt::TextColorRole - 文本颜色(同ForegroundRole)
Qt::BackgroundColorRole - 背景颜色(同BackgroundRole)
单角色+多数据
// 自定义数据项类(MyItem.h)
class MyItem {
public:
QString name;
int age;
QString address;
// 其他角色数据...
};
QList<MyItem> m_items;
QVariant MyModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) return QVariant();
const MyItem &item = m_items[index.row()]; // 获取数据项
if(role==Qt::DisplayRole||role==Qt::EditRole)
return {item.name+" "+QString::number(item.age)+" "+item.address};
else
return QVariant();
}
}
多角色
MyItem就是容器里存储的数据
// 自定义数据项类(MyItem.h)
class MyItem {
public:
QString displayText; // DisplayRole 对应的显示文本
QIcon icon; // DecorationRole 对应的图标
QString toolTip; // ToolTipRole 对应的提示文本
QVariant editValue; // EditRole 对应的可编辑值
// 其他角色数据...
};
QList<MyItem> m_items;
QVariant MyModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) return QVariant();
const MyItem &item = m_items[index.row()]; // 获取数据项
switch (role) {
case Qt::DisplayRole:
return item.displayText; // 返回显示文本
case Qt::DecorationRole:
return item.icon; // 返回图标
case Qt::ToolTipRole:
return item.toolTip; // 返回提示文本
case Qt::EditRole:
return item.editValue; // 返回可编辑值
default:
return QVariant();
}
}
单角色
QVariant MyStringListModel::data(const QModelIndex &index, int role) const {
//判断索引是否有效
if (index.row()<0||index.row()>=lst.count()||!index.isValid()) {
return {}; // 无效索引返回空值
}
//判断role是否有效
if (role == Qt::DisplayRole||role==Qt::EditRole) {
return lst[index.row()]; // 返回对应行的字符串
}
else //如果role无效, 返回空值
return {};
}
virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const = 0
创建并返回父索引 parent 下位于 row 行 column 列的子项的索引。
- 表格模型和列表模型
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override {
// 必要条件检查:
// 1. parent 必须无效(表格模型不支持父子层级)
// 2. row 和 column 必须在有效范围内
if (parent.isValid()) {
return QModelIndex(); // 表格模型无子层级,parent 有效时返回无效索引
}
if (row < 0 || row >= rowCount() || column < 0 || column >= columnCount()) {
return QModelIndex(); // 行或列越界
}
// 创建索引:行号、列号、数据指针(此处用 nullptr)
return createIndex(row, column, nullptr);
}
virtual QModelIndex parent(const QModelIndex &index) const = 0
返回索引 index 的父索引。根节点的父索引为无效的 QModelIndex()。
- 列表和表格模型
QModelIndex parent(const QModelIndex &index) const override {
// 列表模型是扁平结构,所有项的父节点均为无效索引
return QModelIndex();
}
模型的父索引
在 Qt 的列表模型(QAbstractListModel
)和表格模型(QAbstractTableModel
)中,由于它们本质是平面结构(不支持树形层级),所有项的父索引(parent
)默认均为无效索引(QModelIndex()
)。
-
列表模型(QAbstractListModel)
模型特性:列表模型是单列、平面结构,所有数据项均为顶级项(无父节点)。因此:parent 必须始终无效:列表模型不支持树形结构,任何有效的 parent 均被视为非法。必须检查 parent:在实现 rowCount() 等带parent索引参数的函数 时,需确保 parent.isValid() 为 false,否则应返回 0 或空数据。
// 列表模型的 rowCount() 实现
int MyListModel::rowCount(const QModelIndex &parent) const {
if (parent.isValid())
return 0; // 父索引有效时返回0(不合法)
return m_data.size(); // 返回实际行数
}
int MyListModel::columnCount(const QModelIndex &parent) const {
// 防御性检查:父索引有效时返回0
if (parent.isValid())
return 0;
// 返回实际列数(列表模型返回1,表格模型返回固定列数)
return m_columns;
}
-
表格模型(QAbstractTableModel)
模型特性:表格模型是二维平面结构,所有单元格的父节点均为根节点(QModelIndex())。因此:parent 必须无效:表格模型同样不支持层级结构,有效的 parent 会导致逻辑错误。必须检查 parent:在 rowCount()、columnCount() 等方法中,需过滤有效的 parent。
// 表格模型的 rowCount() 实现
int MyTableModel::rowCount(const QModelIndex &parent) const {
if (parent.isValid())
return 0; // 父索引有效时返回0(不合法)
return m_rows; // 返回实际行数
}
int MyTableModel::columnCount(const QModelIndex &parent) const {
// 防御性检查:父索引有效时返回0
if (parent.isValid())
return 0;
// 返回实际列数(列表模型返回1,表格模型返回固定列数)
return m_columns;
}
3. 对于树状模型:
必须验证parent索引的有效性
还需要确认parent索引实际指向的是否为有效节点
通常需要检查parent.isValid()和parent.internalPointer()
// 在树状模型中
if (!parent.isValid()) {
// 处理根节点的情况
} else {
// 验证parent索引并处理子节点的情况
Item *parentItem = static_cast<Item*>(parent.internalPointer());
if (!parentItem)
return 默认值;
// 继续处理...
}
4.只要带const QModelIndex &parent
参数的函数都必须验证其有效性,原因参考上面
//代码通常为
// 在平面模型中
if (parent.isValid())
return 0; // 或其他默认值,根据函数法人返回值决定
5. 为什么必须检查 parent
?
防御性编程
- 防止非法访问:即使模型是平面结构,某些视图或用户代码可能错误传递有效
parent
(例如拖放操作中的临时索引)。 - 兼容框架约定:Qt 要求所有模型正确处理
parent
,即使逻辑上不需要层级结构。
示例场景
- 若忽略
parent
检查,当视图意外传递有效parent
时:rowCount(parent)
可能返回错误行数,导致视图渲染异常。index()
可能生成非法索引,引发崩溃。
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const = 0
返回父索引 parent 下的行数。
- 列表模型
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
// 仅当 parent 无效时返回行数(数据项数)
if (parent.isValid()) {
return 0;
}
return m_data.size();//你的数据结构的size
}
- 表格模型
QVector<QVector<QString>> m_data = {
{"张三", "25"},
{"李四", "30"},
{"王五", "28"}
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
// 仅当 parent 无效时返回行数
if (parent.isValid()) {
return 0;
}
return m_data.size();
}
数据操作
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)
设置索引 index 的角色 role 的数据为 value,返回是否成功。
注意:index.row()的有效范围为[0,rowCount()-1](列表模型)
表格模型还要关注index.column()同样为[0,columnCount()-1]
或者直接使用index.isValid()来判断
如果具有多个角色,就需要对每个角色进行独立的操作(列表模型)
列表模型
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!index.isValid()) {
return false;
}
MyItem &item = m_items[index.row()];
bool success = false;
switch (role) {
case Qt::DisplayRole:
item.displayText = value.toString();
success = true;
break;
case Qt::DecorationRole:
item.icon = value.value<QIcon>();
success = true;
break;
case Qt::ToolTipRole:
item.toolTip = value.toString();
success = true;
break;
// 其他角色处理...
default:
success = false; // 不支持的角色
}
if (success) {
// 不立即触发信号,由 setItemData 统一处理
// emit dataChanged(index, index, {role});
}
return success;
}
表格模型
// 数据存储结构:每个单元格存储多个角色的数据(例如 DisplayRole, EditRole, 自定义角色)
struct CellData {
QMap<int, QVariant> roles; // 角色到数据的映射
};
QVector<QVector<CellData>> m_data;
// 初始化示例数据(3行2列)
TableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
m_data.resize(3);
for (int row = 0; row < 3; ++row) {
m_data[row].resize(2);
for (int col = 0; col < 2; ++col) {
// 默认设置 DisplayRole 和 EditRole
m_data[row][col].roles[Qt::DisplayRole] = QString("R%1C%2").arg(row).arg(col);
m_data[row][col].roles[Qt::EditRole] = QString("Edit-R%1C%2").arg(row).arg(col);
}
}
}
// 设置单个角色数据
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override {
if (!index.isValid() || index.row() >= rowCount() || index.column() >= columnCount()) {
return false; // 索引越界
}
// 更新指定角色的数据
m_data[index.row()][index.column()].roles[role] = value;
// 通知视图数据已更改
emit dataChanged(index, index, {role});
return true;
}
virtual QMap<int, QVariant> itemData(const QModelIndex &index) const
返回索引 index 的所有角色及其数据的映射(QMap<int, QVariant>)。
列表模型
步骤
- 验证索引有效性。
- 遍历预定义的支持角色,调用data方法获取数据。
- 过滤有效数据,存入QMap并返回。
QMap<int, QVariant> MyModel::itemData(const QModelIndex &index) const {
QMap<int, QVariant> rolesData;
// 1. 检查索引有效性
if (!index.isValid()) {
return rolesData;
}
// 2. 定义模型支持的角色列表,根据具体情况添加,例如QStringListModel只需DisplayRole和EditRole
const QList<int> supportedRoles = {
Qt::DisplayRole,
Qt::EditRole,
Qt::DecorationRole,
Qt::ToolTipRole
// 添加其他支持的角色...
};
// 3. 遍历角色并收集数据
for (int role : supportedRoles) {
QVariant value = data(index, role);
if (value.isValid()) {
rolesData.insert(role, value);
}
}
return rolesData;
}
表格模型
// 数据存储结构:每个单元格存储多个角色的数据(例如 DisplayRole, EditRole, 自定义角色)
struct CellData {
QMap<int, QVariant> roles; // 角色到数据的映射
};
QVector<QVector<CellData>> m_data;
// 初始化示例数据(3行2列)
TableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
m_data.resize(3);
for (int row = 0; row < 3; ++row) {
m_data[row].resize(2);
for (int col = 0; col < 2; ++col) {
// 默认设置 DisplayRole 和 EditRole
m_data[row][col].roles[Qt::DisplayRole] = QString("R%1C%2").arg(row).arg(col);
m_data[row][col].roles[Qt::EditRole] = QString("Edit-R%1C%2").arg(row).arg(col);
}
}
}
// 获取索引处所有角色的数据
QMap<int, QVariant> itemData(const QModelIndex &index) const override {
QMap<int, QVariant> roles;
if (!index.isValid()) {
return roles; // 返回空映射
}
// 返回该单元格存储的所有角色数据
roles = m_data[index.row()][index.column()].roles;
return roles;
}
virtual bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles)
批量设置索引 index 的多角色数据(roles 为角色到值的映射),返回是否成功。
列表模型
步骤
- 验证索引和输入数据有效性。
- 逐个设置角色数据,记录成功与否。
- 统一发出数据变更信号,优化性能。
bool MyModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles) {
// 1. 检查索引和输入有效性
if (!index.isValid() || roles.isEmpty()) {
return false;
}
bool allSuccess = true;
QList<int> changedRoles;
// 2. 遍历角色并设置数据
for (auto it = roles.constBegin(); it != roles.constEnd(); ++it) {
int role = it.key();
const QVariant &value = it.value();
// 调用 setData 设置单个角色
if (setData(index, value, role)) {
changedRoles.append(role); // 记录成功设置的角色
} else {
allSuccess = false; // 任一角色设置失败则整体失败
}
}
//这里暂时不再发射信号,让setData发射信号,否则当你只对某一个项修改数据时就会产生冲突
/* 3. 统一发出数据变更信号(避免多次触发)
if (!changedRoles.isEmpty()) {
emit dataChanged(index, index, changedRoles);
}*/
return allSuccess;
}
表格模型
// 数据存储结构:每个单元格存储多个角色的数据(例如 DisplayRole, EditRole, 自定义角色)
struct CellData {
QMap<int, QVariant> roles; // 角色到数据的映射
};
QVector<QVector<CellData>> m_data;
// 初始化示例数据(3行2列)
TableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
m_data.resize(3);
for (int row = 0; row < 3; ++row) {
m_data[row].resize(2);
for (int col = 0; col < 2; ++col) {
// 默认设置 DisplayRole 和 EditRole
m_data[row][col].roles[Qt::DisplayRole] = QString("R%1C%2").arg(row).arg(col);
m_data[row][col].roles[Qt::EditRole] = QString("Edit-R%1C%2").arg(row).arg(col);
}
}
}
// 批量设置多个角色数据
bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles) override {
if (!index.isValid()) {
return false;
}
// 记录旧数据以便回滚
QMap<int, QVariant> oldRoles = m_data[index.row()][index.column()].roles;
// 尝试逐个设置角色数据
for (auto it = roles.begin(); it != roles.end(); ++it) {
if (!setData(index, it.value(), it.key())) {
// 如果某个角色设置失败,回滚并返回 false
m_data[index.row()][index.column()].roles = oldRoles;
return false;
}
}
return true;
}
virtual bool clearItemData(const QModelIndex &index) (Qt 6.0+)
清除索引 index 的所有角色数据,返回是否成功。
对于单一角色模型例如QStringListModel就只需要displayrole和editRole即可,setData里只处理他们即可
列表模型
步骤
- 检查索引有效性:若索引无效,直接返回 false。
- 获取模型支持的角色列表:通常与 itemData 中定义的角色一致。
- 遍历角色并清除数据:对每个支持的角色调用 setData,将值设为 QVariant()(或模型定义的默认值)。
- 统一触发数据变更信号:优化性能,避免多次触发 dataChanged。
- 返回操作结果:若所有角色均成功清除返回 true,否则 false。
bool MyModel::clearItemData(const QModelIndex &index) {
// 1. 检查索引有效性
if (!index.isValid()) {
return false;
}
// 2. 定义支持清除的角色列表(需与 itemData/setItemData 一致)
const QList<int> clearableRoles = {
Qt::DisplayRole,
Qt::EditRole,
Qt::DecorationRole,
Qt::ToolTipRole
// 添加其他支持清除的角色...
};
bool allSuccess = true;
QVector<int> clearedRoles;
// 3. 遍历角色并清除数据
for (int role : clearableRoles) {
// 调用 setData 将角色数据设为无效
bool success = setData(index, QVariant(), role);
if (success) {
clearedRoles.append(role);
} else {
allSuccess = false; // 存在角色清除失败
}
}
// 4. 统一触发数据变更信号
if (!clearedRoles.isEmpty()) {
emit dataChanged(index, index, clearedRoles);
}
return allSuccess;
}
- 需要与 setData 协同工作
确保 setData 能正确处理将数据设为 QVariant() 的请求,例如:
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!index.isValid()) {
return false;
}
MyItem &item = m_items[index.row()];
bool success = false;
switch (role) {
case Qt::DisplayRole:
// 当vaiue.isValid()=false说明是清除操作,否则是单一设置数据操作
if(!value.isValid())
{
item.displayText="";
}
else
{
item.displayText = value.toString();
Q_EMIT dataChanged(index, index, { Qt::DisplayRole}); // 触发数据改变信号
}
success = true;
break;
case Qt::EditRole:
//逻辑同上
...
break;
// 其他角色处理...
}
return success;
}
class MyItem {
public:
QString displayText;
QIcon icon;
QString toolTip;
QVariant editValue;
};
class MyModel : public QAbstractListModel {
Q_OBJECT
public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
private:
QList<MyItem> m_items; // 数据存储容器
};
-
关键注意事项
(1) 角色列表一致性
clearItemData 中清除的角色必须与 itemData/setItemData 支持的角色一致,避免遗漏或冗余。(2) 性能优化
批量操作:通过一次 dataChanged 信号通知视图更新,而非每次 setData 都触发。无效值处理:在 setData 中明确如何处理 QVariant()(如设为空字符串、重置为默认值等)。
(3) 返回值逻辑
若所有角色均成功清除,返回 true。若任意角色清除失败(如只读角色),返回 false,但已清除的角色仍会生效。
(4) Qt 版本兼容性
clearItemData 是 Qt 6 新增函数,需确保项目使用 Qt 6.0 或更高版本。若需兼容 Qt 5,需通过其他方式实现类似功能(如手动调用 setData 逐个清除角色)。
表格模型
// 数据存储结构:每个单元格存储多个角色的数据(例如 DisplayRole, EditRole, 自定义角色)
struct CellData {
QMap<int, QVariant> roles; // 角色到数据的映射
};
QVector<QVector<CellData>> m_data;
// 初始化示例数据(3行2列)
TableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
m_data.resize(3);
for (int row = 0; row < 3; ++row) {
m_data[row].resize(2);
for (int col = 0; col < 2; ++col) {
// 默认设置 DisplayRole 和 EditRole
m_data[row][col].roles[Qt::DisplayRole] = QString("R%1C%2").arg(row).arg(col);
m_data[row][col].roles[Qt::EditRole] = QString("Edit-R%1C%2").arg(row).arg(col);
}
}
}
// ==================== 核心方法实现 ====================
// 清除指定索引的所有角色数据(Qt 6.0+)
bool clearItemData(const QModelIndex &index) override {
if (!index.isValid()) {
return false; // 索引无效
}
// 清除该单元格的所有角色数据
m_data[index.row()][index.column()].roles.clear();
// 通知视图数据已更改
emit dataChanged(index, index);
return true;
}
结构修改
virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex())
在父索引 parent 下插入 count 行到 row 行位置,返回是否成功。需调用 beginInsertRows() 和 endInsertRows()。
注意:在Qt模型中,row的有效插入位置范围是 0 ≤ row ≤ rowCount(允许在末尾追加)
当 row == rowCount:
- 允许插入,表示在列表末尾追加新行(例如列表有3个元素时,row=3会在末尾添加新项)。
当 row > rowCount:
- 超出列表物理范围(例如列表有3个元素时,row=4无意义),必须返回 false 拒绝操作。
通常会在此函数内部调用存储数据的容器的insert方法例如Qlist,而且QList等的insert方法会自动调整容器大小并管理内存,无需担心row == rowCount时插入行失败
- 列表模型
QVector<QString> m_data = {"Item 1", "Item 2", "Item 3"};
// ==================== 行操作 ====================
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
// 检查:parent 必须无效,行范围有效,count > 0
if (parent.isValid() || row < 0 || row > rowCount() || count <= 0) {
return false;
}
// 通知视图:即将插入行
beginInsertRows(parent, row, row + count - 1);
// 插入空数据
for (int i = 0; i < count; ++i) {
m_data.insert(row + i, "New Item");
}
endInsertRows();
return true;
}
- 表格模型
QVector<QVector<QString>> m_data = {
{"A1", "B1"},
{"A2", "B2"},
{"A3", "B3"}
};
// ==================== 行操作 ====================
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
// 检查:parent 必须无效,行范围有效,count > 0
if (parent.isValid() || row < 0 || row > rowCount() || count <= 0) {
return false;
}
// 通知视图:即将插入行
beginInsertRows(parent, row, row + count - 1);
// 插入空行(每行包含 columnCount() 列)
for (int i = 0; i < count; ++i) {
m_data.insert(row + i, QVector<QString>(columnCount(), ""));
}
endInsertRows();
return true;
}
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex())
从父索引 parent 下移除从 row 行开始的 count 行,返回是否成功。需调用 beginRemoveRows() 和 endRemoveRows()。
row:必须满足 0 ≤ row< rowCount()(严格小于当前行数)。
如果 row >= rowCount(),返回 false(无法删除不存在的行)。
- 列表模型
QVector<QString> m_data = {"Item 1", "Item 2", "Item 3"};
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
// 检查:parent 必须无效,行范围有效,count > 0
if (parent.isValid() || row < 0 || (row + count) > rowCount() || count <= 0) {
return false;
}
// 通知视图:即将删除行
beginRemoveRows(parent, row, row + count - 1);
// 删除数据
for (int i = 0; i < count; ++i) {
m_data.removeAt(row);
}
endRemoveRows();
return true;
}
- 表格模型
QVector<QVector<QString>> m_data = {
{"A1", "B1"},
{"A2", "B2"},
{"A3", "B3"}
};
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
// 检查:parent 必须无效,行范围有效,count > 0
if (parent.isValid() || row < 0 || (row + count) > rowCount() || count <= 0) {
return false;
}
// 通知视图:即将删除行
beginRemoveRows(parent, row, row + count - 1);
// 删除行
for (int i = 0; i < count; ++i) {
m_data.removeAt(row);
}
endRemoveRows();
return true;
}
virtual bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex())
类似 insertRows,但操作列。
在Qt模型中,insertColumns
的处理逻辑与 insertRows
类似但存在关键区别,具体取决于模型类型:
1. 对于表格模型(QAbstractTableModel
)
-
条件判断:
若模型支持多列,insertColumns
的条件应为column > columnCount(parent)
,而非>=
。
原因:允许在末尾追加列(column = columnCount
是合法位置),而column > columnCount
表示越界。bool MyTableModel::insertColumns(int column, int count, const QModelIndex &parent) { if (count < 1 || column < 0 || column > columnCount(parent)) return false; beginInsertColumns(parent, column, column + count - 1); // 插入列的逻辑... endInsertColumns(); return true; }
QVector<QVector<QString>> m_data = {
{"A1", "B1"},
{"A2", "B2"},
{"A3", "B3"}
};
// ==================== 列操作 ====================
bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override {
// 检查:parent 必须无效,列范围有效,count > 0
if (parent.isValid() || column < 0 || column > columnCount() || count <= 0) {
return false;
}
// 通知视图:即将插入列
beginInsertColumns(parent, column, column + count - 1);
// 为每一行插入空列
for (int i = 0; i < rowCount(); ++i) {
for (int j = 0; j < count; ++j) {
m_data[i].insert(column + j, "");
}
}
endInsertColumns();
return true;
}
2. 对于列表模型(QAbstractListModel
或 QStringListModel
)
- 列操作的默认行为:
列表模型通常为单列(columnCount()
固定返回 1),不支持插入列。insertColumns
的默认实现直接返回false
,因列结构不可变。- 若强行调用,即使
column <= columnCount()
,也会失败。
QVector<QString> m_data = {"Item 1", "Item 2", "Item 3"};
// ==================== 列操作(列表模型不支持) ====================
bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override {
// 列表模型固定一列,插入列操作无效
return false;
}
virtual bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex())
类似 removeRows,但操作列。
列表模型(QStringListModel/QAbstractListModel):列操作无意义: 默认返回 false,因为列数固定为 1
QVector<QString> m_data = {"Item 1", "Item 2", "Item 3"};
bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override {
// 列表模型固定一列,删除列操作无效
return false;
}
表格模型
- column:必须满足 0 ≤ column< columnCount()(严格小于当前列数)。
- 如果 column>= columnCount(),返回 false(无法删除不存在的列)。
QVector<QVector<QString>> m_data = {
{"A1", "B1"},
{"A2", "B2"},
{"A3", "B3"}
};
bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override {
// 检查:parent 必须无效,列范围有效,count > 0
if (parent.isValid() || column < 0 || (column + count) > columnCount() || count <= 0) {
return false;
}
// 通知视图:即将删除列
beginRemoveColumns(parent, column, column + count - 1);
// 删除每行的指定列
for (int i = 0; i < rowCount(); ++i) {
for (int j = 0; j < count; ++j) {
m_data[i].removeAt(column);
}
}
endRemoveColumns();
return true;
}
bool insertRow(int row, const QModelIndex &parent = QModelIndex())
便捷函数,调用 insertRows(row, 1, parent)。
bool insertColumn(int column, const QModelIndex &parent = QModelIndex())
便捷函数,调用 insertColumns(column, 1, parent)。
bool removeRow(int row, const QModelIndex &parent = QModelIndex())
便捷函数,调用 removeRows(row, 1, parent)。
bool removeColumn(int column, const QModelIndex &parent = QModelIndex())
便捷函数,调用 removeColumns(column, 1, parent)。
索引与导航
virtual QModelIndex sibling(int row, int column, const QModelIndex &index) const
返回与 index 同父项、位于 row 行 column 列的兄弟索引。
在平面模型中(QAbstractTableModel和QAbstractListModel):
- 相对简单,因为所有项目都在根级别
- 表格模型:获取同一行不同列或同一列不同行的项目,因为父项都一样所以可以根据row和column来获取row行column列的项的索引
QVector<QVector<QString>> m_data = {
{"A1", "B1"},
{"A2", "B2"},
{"A3", "B3"}
};
// ==================== sibling 方法 ====================
QModelIndex sibling(int row, int column, const QModelIndex &index) const override {
if (!index.isValid() || row < 0 || column < 0 || column >= columnCount()) {
return QModelIndex();
}
// 返回同一父节点下的兄弟索引(行或列变化)
return createIndex(row, column, index.internalPointer());
}
- 列表模型:主要用于获取不同行的项目(通常只有一列),因为父项都一样所以可以根据row来获取不同行的项,例如下图所示就是获取列表中第1行(从0开始)的项的索引
QVector<QString> m_data = {"Item 1", "Item 2", "Item 3"};
// ==================== sibling 方法 ====================
QModelIndex sibling(int row, int column, const QModelIndex &index) const override {
// 列表模型只有一列,column 必须为 0
if (column != 0 || !index.isValid()) {
return QModelIndex();
}
// 返回同一父节点下的兄弟索引(行号变化,列固定为 0)
return createIndex(row, 0, index.internalPointer());
}
在树形模型中:
- 作用更加明显,用于在同一父节点下的兄弟节点间导航
- 避免需要先获取父节点,再从父节点获取子节点的复杂过程
主要应用场景:
- 视图组件(如QTreeView、QTableView)使用它实现键盘导航
- 选择模型中用于选择相邻项目
- 代理编辑器用于在编辑完成后移动到相邻单元格
virtual QModelIndex buddy(const QModelIndex &index) const
返回给定索引的"伙伴"索引,通常用于将编辑操作从一个单元格重定向到另一个单元格。
主要功能:
- 默认实现直接返回原始索引
- 当视图组件要编辑一个单元格时,实际会编辑该单元格的"伙伴"
- 允许模型控制哪些单元格可以接收编辑操作
示例:
class CheckableTableModel : public QAbstractTableModel
{
public:
// 其他必要的模型实现...
QModelIndex buddy(const QModelIndex &index) const override
{
// 如果用户点击了描述列(第2列),将操作重定向到同一行的复选框列(第0列)
if (index.column() == 1)
return createIndex(index.row(), 0, index.internalPointer());
// 否则使用默认行为
return index;
}
};
在此示例中,当用户尝试编辑描述列时,视图会自动将操作重定向到同行的复选框。这种模式常用于:
- 文本标签和对应控件之间的关系
- 让用户通过点击描述文本来切换复选框
- 实现点击一列但实际编辑另一列的交互行为
bool hasIndex(int row, int column, const QModelIndex &parent = QModelIndex()) const
检查父索引 parent 下是否存在 row 行 column 列的项,返回布尔值。
virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const
检查父索引 parent 是否有子项,默认通过 rowCount() 和 columnCount() 判断。
拖放与MIME数据处理
virtual QMimeData *mimeData(const QModelIndexList &indexes) const
将索引列表 indexes 转换为 QMimeData,用于拖放操作。
virtual bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
检查是否可以在 parent 的 row 行 column 列接受拖放的 data,动作是 action。
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
处理拖放的数据 data,将其插入到指定位置,返回是否成功。
virtual QStringList mimeTypes() const
返回支持的 MIME 类型列表(如 text/plain)。
virtual Qt::DropActions supportedDragActions() const
返回支持的拖拽动作(如 Qt::CopyAction)。
virtual Qt::DropActions supportedDropActions() const
返回支持的放置动作。
// 支持的拖放动作(移动或复制)
Qt::DropActions supportedDragActions() const override {
return Qt::CopyAction | Qt::MoveAction;
}
Qt::DropActions supportedDropActions() const override {
return Qt::CopyAction | Qt::MoveAction;
}
表头数据
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
返回表头数据:section(行/列号)、orientation(水平/垂直)、role。
virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole)
设置表头数据,返回是否成功。
- 表格模型
QVector<QVector<QString>> m_data = {
{"A1", "B1"},
{"A2", "B2"},
{"A3", "B3"}
};
QStringList m_horizontalHeaders = {"Column 1", "Column 2"}; // 水平表头
QStringList m_verticalHeaders; // 垂直表头
// ==================== 表头方法 ====================
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override {
if (role != Qt::DisplayRole) {
return QVariant();
}
if (orientation == Qt::Horizontal && section < m_horizontalHeaders.size()) {
return m_horizontalHeaders[section];
}
if (orientation == Qt::Vertical && section < m_verticalHeaders.size()) {
return m_verticalHeaders[section];
}
return QVariant();
}
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override {
if (role != Qt::EditRole) {
return false;
}
if (orientation == Qt::Horizontal) {
if (section >= m_horizontalHeaders.size()) {
m_horizontalHeaders.resize(section + 1);
}
m_horizontalHeaders[section] = value.toString();
emit headerDataChanged(orientation, section, section);
return true;
} else if (orientation == Qt::Vertical) {
if (section >= m_verticalHeaders.size()) {
m_verticalHeaders.resize(section + 1);
}
m_verticalHeaders[section] = value.toString();
emit headerDataChanged(orientation, section, section);
return true;
}
return false;
}
- 列表模型
QVector<QString> m_data = {"Item 1", "Item 2", "Item 3"};
QStringList m_verticalHeaders; // 存储垂直表头(行头)数据
// ==================== 表头方法 ====================
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override {
if (role != Qt::DisplayRole) {
return QVariant();
}
// 垂直表头(行号)
if (orientation == Qt::Vertical && section < m_verticalHeaders.size()) {
return m_verticalHeaders.value(section);
}
// 水平表头固定为 "Items"
if (orientation == Qt::Horizontal && section == 0) {
return "Items";
}
return QVariant();
}
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override {
if (role != Qt::EditRole) {
return false;
}
// 仅支持设置垂直表头
if (orientation == Qt::Vertical) {
if (section >= m_verticalHeaders.size()) {
m_verticalHeaders.resize(section + 1);
}
m_verticalHeaders[section] = value.toString();
emit headerDataChanged(orientation, section, section);
return true;
}
return false;
}
懒加载
virtual bool canFetchMore(const QModelIndex &parent) const
检查是否有更多数据可懒加载到父索引 parent 下。
virtual void fetchMore(const QModelIndex &parent)
加载更多数据到父索引 parent 下。
- 列表模型
#include <QAbstractListModel>
#include <QVector>
class LazyListModel : public QAbstractListModel {
Q_OBJECT
public:
// 当前已加载的数据和总数据量
QVector<QString> m_loadedData;
int m_totalCount = 100; // 假设总共有 100 条数据
int m_pageSize = 20; // 每页加载 20 条
// 检查是否还有更多数据可加载
bool canFetchMore(const QModelIndex &parent) const override {
// 列表模型 parent 必须无效,且已加载数据量小于总数
return !parent.isValid() && (m_loadedData.size() < m_totalCount);
}
// 加载更多数据
void fetchMore(const QModelIndex &parent) override {
if (!canFetchMore(parent)) {
return;
}
// 计算剩余未加载的数量
int remaining = m_totalCount - m_loadedData.size();
int itemsToFetch = qMin(m_pageSize, remaining);
// 模拟从数据源加载新数据(例如数据库或网络)
QVector<QString> newData;
for (int i = 0; i < itemsToFetch; ++i) {
newData.append(QString("Item %1").arg(m_loadedData.size() + i + 1));
}
// 通知视图即将插入新行
beginInsertRows(QModelIndex(), m_loadedData.size(), m_loadedData.size() + itemsToFetch - 1);
m_loadedData.append(newData);
endInsertRows();
}
};
- 表格模型
#include <QAbstractTableModel>
#include <QVector>
class LazyTableModel : public QAbstractTableModel {
Q_OBJECT
public:
// 当前已加载的数据和总行数
QVector<QVector<QString>> m_loadedData;
int m_totalRows = 50; // 总共有 50 行
int m_pageSize = 10; // 每页加载 10 行
int m_columns = 3; // 固定 3 列
// 检查是否还有更多数据可加载
bool canFetchMore(const QModelIndex &parent) const override {
// 表格模型 parent 必须无效,且已加载行数小于总数
return !parent.isValid() && (m_loadedData.size() < m_totalRows);
}
// 加载更多数据
void fetchMore(const QModelIndex &parent) override {
if (!canFetchMore(parent)) {
return;
}
// 计算剩余未加载的行数
int remaining = m_totalRows - m_loadedData.size();
int rowsToFetch = qMin(m_pageSize, remaining);
// 模拟从数据源加载新数据
QVector<QVector<QString>> newRows;
for (int row = 0; row < rowsToFetch; ++row) {
QVector<QString> rowData;
for (int col = 0; col < m_columns; ++col) {
rowData.append(QString("R%1C%2").arg(m_loadedData.size() + row).arg(col));
}
newRows.append(rowData);
}
// 通知视图即将插入新行
beginInsertRows(QModelIndex(), m_loadedData.size(), m_loadedData.size() + rowsToFetch - 1);
m_loadedData.append(newRows);
endInsertRows();
}
};
其他功能
virtual void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const (Qt 6.0+)
高效获取索引 index 的多个角色数据,填充到 roleDataSpan 中。
- 列表模型
struct ItemData {
QString text; // DisplayRole 数据
QColor bgColor; // BackgroundRole 数据
};
QVector<ItemData> m_data = {
{"Apple", QColor(255, 0, 0)},
{"Banana", QColor(255, 255, 0)},
{"Cherry", QColor(255, 0, 255)}
};
// ==================== multiData ====================
// 一次性获取多个角色数据(Qt 6.0+)
void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const override {
if (!index.isValid()) return;
const ItemData &item = m_data[index.row()];
for (QModelRoleData &roleData : roleDataSpan) {
switch (roleData.role()) {
case Qt::DisplayRole:
roleData.setData(item.text);
break;
case Qt::BackgroundRole:
roleData.setData(item.bgColor);
break;
}
}
}
- 表格模型
struct CellData {
QString text; // DisplayRole 数据
QString tooltip; // ToolTipRole 数据
};
QVector<QVector<CellData>> m_data = {
{{"A1", "Tooltip A1"}, {"B1", "Tooltip B1"}},
{{"A2", "Tooltip A2"}, {"B2", "Tooltip B2"}},
{{"A3", "Tooltip A3"}, {"B3", "Tooltip B3"}}
};
// ==================== multiData ====================
// 一次性获取多个角色数据(Qt 6.0+)
void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const override {
if (!index.isValid()) return;
const CellData &cell = m_data[index.row()][index.column()];
for (QModelRoleData &roleData : roleDataSpan) {
switch (roleData.role()) {
case Qt::DisplayRole:
roleData.setData(cell.text);
break;
case Qt::ToolTipRole:
roleData.setData(cell.tooltip);
break;
}
}
}
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchStartsWith|Qt::MatchWrap) const
从 start 索引开始搜索 role 数据匹配 value 的项,返回最多 hits 个结果,匹配方式由 flags 控制。
- 表格模型
struct CellData {
QString text; // DisplayRole 数据
QString tooltip; // ToolTipRole 数据
};
QVector<QVector<CellData>> m_data = {
{{"A1", "Tooltip A1"}, {"B1", "Tooltip B1"}},
{{"A2", "Tooltip A2"}, {"B2", "Tooltip B2"}},
{{"A3", "Tooltip A3"}, {"B3", "Tooltip B3"}}
};
// ==================== match ====================
// 查找匹配项(例如查找包含 "B" 的单元格)
QModelIndexList match(const QModelIndex &start, int role,
const QVariant &value, int hits = 1,
Qt::MatchFlags flags = Qt::MatchStartsWith | Qt::MatchWrap) const override {
QModelIndexList matches;
if (role != Qt::DisplayRole) return matches;
const QString searchText = value.toString();
for (int row = start.isValid() ? start.row() : 0; row < m_data.size(); ++row) {
for (int col = start.isValid() ? start.column() : 0; col < m_data[row].size(); ++col) {
const QString &cellText = m_data[row][col].text;
bool matched = false;
switch (flags & Qt::MatchTypeMask) {
case Qt::MatchStartsWith:
matched = cellText.startsWith(searchText, Qt::CaseInsensitive);
break;
case Qt::MatchContains:
matched = cellText.contains(searchText, Qt::CaseInsensitive);
break;
case Qt::MatchExactly:
matched = (cellText.compare(searchText, Qt::CaseInsensitive) == 0);
break;
}
if (matched) {
matches.append(createIndex(row, col));
if (matches.size() >= hits) return matches;
}
}
}
return matches;
}
- 列表模型
struct ItemData {
QString text; // DisplayRole 数据
QColor bgColor; // BackgroundRole 数据
};
QVector<ItemData> m_data = {
{"Apple", QColor(255, 0, 0)},
{"Banana", QColor(255, 255, 0)},
{"Cherry", QColor(255, 0, 255)}
};
// ==================== match ====================
// 查找匹配项(例如查找以 "A" 开头的项)
QModelIndexList match(const QModelIndex &start, int role,
const QVariant &value, int hits = 1,
Qt::MatchFlags flags = Qt::MatchStartsWith | Qt::MatchWrap) const override {
QModelIndexList matches;
if (role != Qt::DisplayRole) return matches;
const QString searchText = value.toString();
for (int row = start.isValid() ? start.row() : 0; row < m_data.size(); ++row) {
const QString &itemText = m_data[row].text;
bool matched = false;
switch (flags & Qt::MatchTypeMask) {
case Qt::MatchStartsWith:
matched = itemText.startsWith(searchText, Qt::CaseInsensitive);
break;
case Qt::MatchContains:
matched = itemText.contains(searchText, Qt::CaseInsensitive);
break;
case Qt::MatchExactly:
matched = (itemText.compare(searchText, Qt::CaseInsensitive) == 0);
break;
}
if (matched) {
matches.append(createIndex(row, 0));
if (matches.size() >= hits) break;
}
}
return matches;
}
virtual QSize span(const QModelIndex &index) const
返回索引 index 单元格的行列跨度(如合并单元格),默认 QSize(1,1)。
跨度 QSize(columns, rows)表示从当前单元格向右合并 columns 列,向下合并 rows 行
被合并的单元格(如第0行第1列)仍需在模型中存在,但视图不会渲染它们。
例如,第0行第1列的 data() 仍然会被调用,但实际显示的是第0行第0列的内容
- 表格模型
QVector<QVector<QString>> m_data = {
{"合并标题", "", "C1"}, // 第0行:第0列跨2列
{"A2", "B2", "C2"},
{"A3", "B3", "C3"},
{"合并行", "合并行", "C4"} // 第3行:第0列跨2行
};
// ==================== span 方法 ====================
QSize span(const QModelIndex &index) const override {
if (!index.isValid()) {
return QSize(1, 1);
}
int row = index.row();
int col = index.column();
// 合并第0行的前两列为标题
if (row == 0 && col == 0) {
return QSize(2, 1); // 跨2列
}
// 合并第3行的前两列跨2行
if (row == 3 && col == 0) {
return QSize(2, 2); // 跨2列、2行(需确保下方有足够行)
}
// 其他情况不合并
return QSize(1, 1);
}
- 列表模型
//默认的 QListView 可能不支持行高合并,需要配合自定义视图或委托(Delegate)实现
QVector<QString> m_data = {
"标题(占2行)",
"内容项1",
"内容项2",
"分隔线(占3行)",
"内容项3"
};
// ==================== span 方法 ====================
QSize span(const QModelIndex &index) const override {
if (!index.isValid()) return QSize(1, 1);
int row = index.row();
// 让索引为 0 的项占据 2 行高度
if (row == 0) {
return QSize(1, 2); // 列宽为 1,行高为 2
}
// 让索引为 3 的项占据 3 行高度
else if (row == 3) {
return QSize(1, 3); // 列宽为 1,行高为 3
}
return QSize(1, 1); // 其他项默认高度
}
virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder)
按 column 列以 order 顺序(升序/降序)排序模型数据。
适用于QAbstractTableModel或QAbstractListModel:
void MyModel::sort(int column, Qt::SortOrder order) override
{
// 防止不必要的排序
if (rowCount() <= 1)
return;
// 通知视图模型即将改变
Q_EMIT layoutAboutToBeChanged();
// 保存持久化索引,以便在排序后更新它们
QModelIndexList persistentIndices = persistentIndexList();
QList<QPair<int, QVariant>> oldRows;
// 保存排序前的行号和数据
for (int row = 0; row < rowCount(); ++row) {
QModelIndex index = this->index(row, column);
oldRows.append(qMakePair(row, data(index, Qt::DisplayRole)));
}
// 根据指定列的数据排序
// 大小写不敏感的比较函数(升序)
auto caseInsensitiveLessThan = [](const QVariant &v1, const QVariant &v2) -> bool {
// 如果是字符串类型,使用大小写不敏感比较
if (v1.typeId() == QMetaType::QString && v2.typeId() == QMetaType::QString) {
return v1.toString().compare(v2.toString(), Qt::CaseInsensitive) < 0;
}
// 对于非字符串类型,尝试转换为同一类型后比较
if (v1.canConvert<QString>() && v2.canConvert<QString>()) {
return v1.toString() < v2.toString();
}
return false; // 或者根据需求返回其他默认值
};
// 大小写不敏感的比较函数(降序)
auto caseInsensitiveGreaterThan = [](const QVariant &v1, const QVariant &v2) -> bool {
if (v1.typeId() == QMetaType::QString && v2.typeId() == QMetaType::QString) {
return v1.toString().compare(v2.toString(), Qt::CaseInsensitive) > 0;
}
// 对于非字符串类型,尝试转换为同一类型后比较
if (v1.canConvert<QString>() && v2.canConvert<QString>()) {
return v1.toString() > v2.toString();
}
return false; // 或者根据需求返回其他默认值
};
// 根据排序顺序选择比较函数
if (order == Qt::AscendingOrder) {
std::sort(oldRows.begin(), oldRows.end(),
[&caseInsensitiveLessThan](const QPair<int, QVariant> &a, const QPair<int, QVariant> &b) {
return caseInsensitiveLessThan(a.second, b.second);
});
} else {
std::sort(oldRows.begin(), oldRows.end(),
[&caseInsensitiveGreaterThan](const QPair<int, QVariant> &a, const QPair<int, QVariant> &b) {
return caseInsensitiveGreaterThan(a.second, b.second);
});
}
// 创建旧行号到新行号的映射
QHash<int, int> rowMapping;
for (int newRow = 0; newRow < oldRows.size(); ++newRow) {
rowMapping[oldRows[newRow].first] = newRow;
}
// 重新排列内部数据
QStringList newData(lst.size());
for (int oldRow = 0,newRow=0; oldRow < lst.size(); ++oldRow) {
newRow = rowMapping[oldRow];
newData[newRow] = lst[oldRow];
}
lst = newData;
// 更新持久化索引
QModelIndexList newPersistentIndices;
for (const QModelIndex &oldIndex : persistentIndices) {
if (oldIndex.isValid() && oldIndex.row() < rowCount()) {
int newRow = rowMapping.value(oldIndex.row(), -1);
newPersistentIndices << ((newRow >= 0) ?
createIndex(newRow, oldIndex.column(), oldIndex.internalPointer())
:QModelIndex());
} else {
newPersistentIndices << QModelIndex();
}
}
// 更新持久化索引并通知视图布局已更改
changePersistentIndexList(persistentIndices, newPersistentIndices);
Q_EMIT layoutChanged();
}
virtual Qt::ItemFlags flags(const QModelIndex &index) const
返回索引 index 的项标志(如 Qt::ItemIsEditable)。
根据需要返回
Qt::ItemFlags MyStringListModel::flags(const QModelIndex &index) const {
//判断索引是否有效
if (index.row()<0||index.row()>=lst.count()) {
//无效则调用父类的flags方法, 返回默认的flags+Qt::ItemIsDropEnabled标志, 表示该项目可放置
return QAbstractItemModel::flags(index)| Qt::ItemIsDropEnabled ;
}
//有效返回默认的flags, 并添加Qt::ItemIsEditable和Qt::ItemIsDragEnabled和Qt::ItemIsDropEnabled标志
//表示该项目可编辑, 可拖动和可放置
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
bool checkIndex(const QModelIndex &index, CheckIndexOptions options = NoOption) const (Qt 6.0+)
检查 index 是否有效,options 控制检查选项(如是否验证父索引)。
virtual QHash<int, QByteArray> roleNames() const
返回角色到名称的映射(如 { Qt::DisplayRole, “display” }),常用于 QML。
virtual QHash<int, QByteArray> roleNames() const
是 Qt 的模型/视图框架(尤其是 QAbstractItemModel 及其子类)中的一个虚函数。它的作用是定义模型中每个角色(role)对应的名称,主要用于 QML 与 C++ 交互时,让 QML 能通过名字访问数据。
1. 作用说明
- 返回值:
QHash<int, QByteArray>
,键是角色的整数值(如Qt::DisplayRole
),值是角色的名称(如"display"
)。 - 用途:在 QML 中通过名称访问模型数据(如
model.display
),而不是只能用数字。 - 常见场景:自定义模型(如 QAbstractListModel/QAbstractTableModel 的子类)并在 QML 中使用时,通常需要重写此函数。
2. 如何重写和使用
1)在你的模型类中重写 roleNames
假设你有如下自定义角色:
enum StudentRoles {
NameRole = Qt::UserRole + 1,
AgeRole,
GradeRole
};
重写 roleNames()
:
QHash<int, QByteArray> StudentTableModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[AgeRole] = "age";
roles[GradeRole] = "grade";
return roles;
}
2)在 data() 函数中支持这些角色,如需编辑也要支持setData()
QVariant StudentTableModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) return QVariant();
const Student &student = students[index.row()];
switch (role) {
case NameRole: return student.name;
case AgeRole: return student.age;
case GradeRole: return student.grade;
// ... 其他角色
default: return QVariant();
}
}
3)在 QML 中使用
如果你将模型暴露给 QML(如通过 setContextProperty 或 QML Register),就可以这样访问:
Text { text: model.name }
Text { text: model.age }
Text { text: model.grade }
3. 总结
- 重写 roleNames() 让你的自定义角色有名字,便于 QML 访问。
- 配合 data() 返回对应数据。
- QML 侧 可直接用名字访问数据字段。
简单示例
// 头文件
enum StudentRoles {
NameRole = Qt::UserRole + 1,
AgeRole,
GradeRole
};
QHash<int, QByteArray> roleNames() const override;
// 源文件
QHash<int, QByteArray> StudentTableModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[AgeRole] = "age";
roles[GradeRole] = "grade";
return roles;
}
bool moveColumn(const QModelIndex &sourceParent, int sourceColumn, const QModelIndex &destinationParent, int destinationChild)
移动列,调用 moveColumns()。
virtual bool moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count, const QModelIndex &destinationParent, int destinationChild)
从 sourceParent 下移动 count 列(从 sourceColumn 开始)到 destinationParent 的 destinationChild 位置。
列表模型(QStringListModel/QAbstractListModel):列操作无意义:默认返回 false,因为列数固定为 1
QVector<QString> m_data = {"Item 1", "Item 2", "Item 3"};
// ==================== 列移动(列表模型不支持) ====================
bool moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count, const QModelIndex &destinationParent, int destinationChild) override {
return false;
}
表格模型
sourceColumn:必须满足 0 ≤ sourceColumn< columnCount()(严格小于当前行数)。
destinationChild: 允许 0 ≤ destinationChild≤ columnCount()(允许移动到末尾之后的位置,行为类似插入)。
- 如果 destinationChild> columnCount(),视为非法。
QVector<QVector<QString>> m_data = {
{"A1", "B1"},
{"A2", "B2"},
{"A3", "B3"}
};
QStringList m_horizontalHeaders = {"Column 1", "Column 2"}; // 水平表头
QStringList m_verticalHeaders; // 垂直表头
// ==================== 列移动 ====================
bool moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count,
const QModelIndex &destinationParent, int destinationChild) override {
// 检查:父节点必须无效,列范围有效
if (sourceParent.isValid() || destinationParent.isValid() ||
sourceColumn < 0 || (sourceColumn + count) > columnCount() ||
destinationChild < 0 || destinationChild > columnCount()) {
return false;
}
// 避免无效移动(例如移动到自身范围内)
if (destinationChild >= sourceColumn && destinationChild < sourceColumn + count) {
return false;
}
// 通知视图:即将移动列
beginMoveColumns(sourceParent, sourceColumn, sourceColumn + count - 1,
destinationParent, destinationChild);
// 执行数据移动(每行的列调整)
for (int row = 0; row < rowCount(); ++row) {
QVector<QString> &rowData = m_data[row];
QVector<QString> movedCols;
for (int i = 0; i < count; ++i) {
movedCols.append(rowData.takeAt(sourceColumn));
}
for (int i = 0; i < count; ++i) {
rowData.insert(destinationChild + i, movedCols[i]);
}
}
// 移动水平表头
QStringList movedHeaders;
for (int i = 0; i < count; ++i) {
movedHeaders.append(m_horizontalHeaders.takeAt(sourceColumn));
}
for (int i = 0; i < count; ++i) {
m_horizontalHeaders.insert(destinationChild + i, movedHeaders[i]);
}
endMoveColumns();
return true;
}
bool moveRow(const QModelIndex &sourceParent, int sourceRow, const QModelIndex &destinationParent, int destinationChild)
移动行,调用 moveRows()。
virtual bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
用于在模型中移动行数据,允许将从sourceRow开始的count行移动到destinationChild之后。
判断条件:
- (sourceRow):必须满足 0 ≤ sourceRow< rowCount()(严格小于当前行数)。
- (destinationChild):允许 0 ≤ destinationChild≤rowCount()(允许移动到末尾之后的位置,行为类似插入)。
- 如果 destinationChild> rowCount(),视为非法
- count必须>0
移动示意图:
主要功能:
- 将指定行数从源位置移动到目标位置
- 可以在不同父节点之间移动(对树形模型有用)
- 自动处理模型通知和视图更新
- 返回操作是否成功
参数说明:
- sourceParent: 源行的父索引
- 列表模型和表格模型值为
QModelIndex()
- 列表模型和表格模型值为
- sourceRow: 要移动的第一行的行号
- count: 要移动的行数
- destinationParent: 目标位置的父索引
- 列表模型和表格模型值为
QModelIndex()
- 列表模型和表格模型值为
- destinationChild: 目标位置的行号(移动后的插入位置)
列表模型
//容器以QStringList为例
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
const QModelIndex &destinationParent, int destinationChild) override
{
// 平面模型中,父索引应该无效
if (sourceParent.isValid() || destinationParent.isValid())
return false;
// 验证参数
if (sourceRow < 0 || sourceRow >=rowCount() ||
destinationChild < 0 || destinationChild > rowCount() ||
count <= 0)
return false;
// 如果源和目标相同或目标是源内部,则不需要移动
if (sourceRow == destinationChild ||
//例如 2 3 4, 4是目标位置但他是移动的3行之中一行
(sourceRow < destinationChild && destinationChild < sourceRow + count))
return false;
// 开始移动操作
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1,
destinationParent, destinationChild);
//方法1:调用容器的move函数
for (int i = 0; i < count; ++i) {
// 移动元素
lst.move(sourceRow , destinationChild);
}
//方法2:
// 1.将要移动的count个项从容器里删除
// 2.使用insert函数将删除的项依次插入到合适的位置
// 结束移动操作
endMoveRows();
return true;
}
表格模型
QVector<QVector<QString>> m_data = {
{"A1", "B1"},
{"A2", "B2"},
{"A3", "B3"}
};
QStringList m_horizontalHeaders = {"Column 1", "Column 2"}; // 水平表头
QStringList m_verticalHeaders; // 垂直表头
// ==================== 行移动 ====================
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
const QModelIndex &destinationParent, int destinationChild) override {
// 检查:父节点必须无效,行范围有效
if (sourceParent.isValid() || destinationParent.isValid() ||
sourceRow < 0 || (sourceRow + count) > rowCount() ||
destinationChild < 0 || destinationChild > rowCount()) {
return false;
}
// 避免无效移动(例如移动到自身范围内)
if (destinationChild >= sourceRow && destinationChild < sourceRow + count) {
return false;
}
// 通知视图:即将移动行
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1,
destinationParent, destinationChild);
// 执行数据移动
QVector<QVector<QString>> movedRows;
for (int i = 0; i < count; ++i) {
movedRows.append(m_data.takeAt(sourceRow));
}
for (int i = 0; i < count; ++i) {
m_data.insert(destinationChild + i, movedRows[i]);
}
endMoveRows();
return true;
}
保护函数
模型结构变更通知函数
这些函数用于在 修改模型结构(插入、删除、移动行/列或重置模型)时,通知视图更新。必须成对调用 beginXxx
和 endXxx
,否则会导致模型状态不一致。
1. 插入列
void beginInsertColumns(const QModelIndex &parent, int first, int last)
void endInsertColumns()
- 作用: 包裹插入列操作,通知视图准备更新。
- 参数:
parent
: 父项的索引,表示在哪个父项下插入列。first
和last
: 插入列的起始和结束位置(闭区间,如插入 1 列时first == last
)。
- 调用时机:
在插入列之前调用beginInsertColumns
,插入完成后调用endInsertColumns
。
2. 插入行
void beginInsertRows(const QModelIndex &parent, int first, int last)
void endInsertRows()
- 作用: 包裹插入行操作,通知视图准备更新。
- 参数: 同插入列,但操作对象是行。
- 示例:
beginInsertRows(parentIndex, 2, 4); // 插入 3 行(行 2、3、4) // ... 插入数据 ... endInsertRows();
3. 移动列
bool beginMoveColumns(const QModelIndex &sourceParent, int sourceFirst, int sourceLast,
const QModelIndex &destinationParent, int destinationChild)
void endMoveColumns()
- 作用: 包裹移动列操作,将列从源父项移动到目标父项。
- 参数:
sourceParent
: 源父项的索引。sourceFirst
和sourceLast
: 要移动列的起始和结束位置。destinationParent
: 目标父项的索引。destinationChild
: 目标位置(插入到目标父项的destinationChild
列之前)。
- 返回值:
bool
,表示移动是否合法(如目标位置有效)。若返回false
,则不应调用endMoveColumns
。 - 示例:
将父项 A 的第 1-2 列移动到父项 B 的第 3 列前:if (beginMoveColumns(parentA, 1, 2, parentB, 3)) { // ... 移动数据 ... endMoveColumns(); }
4. 移动行
bool beginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast,
const QModelIndex &destinationParent, int destinationChild)
void endMoveRows()
- 作用: 类似
beginMoveColumns
,但操作对象是行。 - 参数 和 返回值: 与移动列类似,
destinationChild
是目标行号。
5. 删除列
void beginRemoveColumns(const QModelIndex &parent, int first, int last)
void endRemoveColumns()
- 作用: 包裹删除列操作,通知视图准备更新。
- 参数: 同插入列,指定要删除的列范围。
6. 删除行
void beginRemoveRows(const QModelIndex &parent, int first, int last)
void endRemoveRows()
- 作用: 包裹删除行操作,参数同插入行。
模型重置函数
7. 重置模型
void beginResetModel()
void endResetModel()
- 作用: 完全重置模型(如数据全部变更),通知视图重新加载所有数据。
- 使用场景:
数据发生根本性变化(如重新加载文件),调用后需重新初始化模型数据。 - 注意:
调用beginResetModel
后,必须调用endResetModel
,否则视图无法正确更新。
持久索引管理
8. 更新单个持久索引
void changePersistentIndex(const QModelIndex &from, const QModelIndex &to)
- 作用: 将持久索引
from
更新为to
。
用于模型结构变化后,手动更新外部持有的持久索引(如拖放操作中的索引)。 - 示例:
当行移动后,原索引失效,需将其映射到新位置:changePersistentIndex(oldIndex, newIndex);
9. 批量更新持久索引
void changePersistentIndexList(const QModelIndexList &from, const QModelIndexList &to)
- 作用: 批量更新持久索引列表。
from
和to
必须一一对应,长度相同。 - 示例:
更新多个索引的位置:QModelIndexList oldIndexes = {index1, index2}; QModelIndexList newIndexes = {newIndex1, newIndex2}; changePersistentIndexList(oldIndexes, newIndexes);
10. 获取所有持久索引
QModelIndexList persistentIndexList() const
- 作用: 返回模型当前所有的持久索引列表。
- 用途:
通常在模型结构变化后,遍历此列表并更新索引(结合changePersistentIndex
)。
索引创建函数
11. 创建模型索引
QModelIndex createIndex(int row, int column, const void *ptr = nullptr) const
QModelIndex createIndex(int row, int column, quintptr id) const
- 作用: 创建自定义模型索引,通常用于关联内部数据。
- 参数:
row
和column
: 索引的位置。ptr
或id
: 内部数据标识符(如对象指针或唯一 ID),可通过QModelIndex::internalPointer()
或QModelIndex::internalId()
获取。
- 示例:
关联数据对象指针:MyData *data = getData(); QModelIndex idx = createIndex(0, 0, data); // ptr 版本 QModelIndex idx2 = createIndex(0, 0, reinterpret_cast<quintptr>(data)); // id 版本
关键注意事项
- 成对调用:
beginXxx
和endXxx
必须严格配对,否则导致视图渲染错误或崩溃。 - 持久索引:
若模型外部持有索引(如通过QPersistentModelIndex
),需在结构变化后手动更新。 - 重置模型:
beginResetModel()
会通知视图丢弃所有缓存,应在数据完全准备好后调用endResetModel()
。
公共槽
virtual void revert()
作用
撤销所有未提交的更改,将模型数据恢复到上一次提交后的状态。
此函数通常用于用户取消编辑操作时(如点击“取消”按钮),丢弃临时修改的数据。
默认行为
基类 QAbstractItemModel
的默认实现 不执行任何操作。
若模型需要支持回滚(例如缓存编辑操作),子类需重写此函数,并实现具体的回滚逻辑。
典型应用场景
- 当用户取消编辑时(如关闭对话框前选择“不保存”)。
- 需要手动回滚多个编辑操作(如事务性操作失败时恢复初始状态)。
示例
void MyModel::revert() {
// 恢复数据到原始状态(例如从备份中还原)
m_data = m_backupData;
// 通知视图数据已恢复
emit dataChanged(index(0, 0), index(rowCount()-1, columnCount()-1));
}
virtual bool submit()
作用
提交所有未提交的更改,将临时修改的数据永久保存到模型或底层数据源中。
此函数通常用于用户确认编辑操作时(如点击“保存”按钮),提交所有挂起的更改。
返回值
true
: 提交成功。false
: 提交失败(例如数据验证未通过或持久化错误)。
默认行为
基类 QAbstractItemModel
的默认实现 直接返回 true
。
若模型需要实现提交逻辑(如将数据写入数据库或文件),子类需重写此函数。
典型应用场景
- 用户确认编辑操作后提交数据。
- 需要显式保存批量修改(如表单提交)。
示例
bool MyModel::submit() {
// 验证数据合法性
if (!validateData(m_modifiedData)) {
return false;
}
// 将修改保存到数据库
if (!saveToDatabase(m_modifiedData)) {
return false;
}
// 清除临时修改
m_modifiedData.clear();
return true;
}
关键区别与联系
函数 | 方向 | 操作内容 | 典型触发场景 |
---|---|---|---|
revert() | 回退 | 丢弃未提交的修改 | 用户取消编辑 |
submit() | 提交 | 保存未提交的修改 | 用户确认编辑 |
与 setData()
的关系
-
setData()
:
直接修改模型中的单个数据项,并触发dataChanged()
信号通知视图更新。
通常不需要显式调用submit()
,除非模型需要延迟提交(如批量操作)。 -
submit()
和revert()
:
用于管理批量修改的提交或回滚,例如:- 在编辑对话框中,用户可能修改多个字段,最后点击“确定”提交 (
submit()
) 或“取消”回滚 (revert()
)。 - 需要将数据一次性保存到外部存储(如数据库事务)。
- 在编辑对话框中,用户可能修改多个字段,最后点击“确定”提交 (
使用注意事项
-
何时需要重写
- 如果模型需要支持事务性编辑(如缓存多个编辑操作,最后统一提交或回滚),需重写这两个函数。
- 如果直接通过
setData()
实时更新数据源(如内存模型),可能无需重写。
-
与视图的交互
- 视图(如
QTableView
)的默认行为可能自动调用submit()
(如编辑完成后焦点离开单元格)。 - 若需手动控制提交/回滚(如对话框中的“保存”和“取消”按钮),需显式调用这两个函数。
- 视图(如
-
错误处理
- 如果
submit()
返回false
,视图可能保持编辑状态,允许用户修正错误。
- 如果
示例代码片段
// 用户点击“保存”按钮
void onSaveClicked() {
if (model->submit()) {
// 提交成功,关闭窗口
close();
} else {
// 提交失败,显示错误
showError("保存失败,请检查数据!");
}
}
// 用户点击“取消”按钮
void onCancelClicked() {
model->revert(); // 丢弃未提交的修改
close();
}
信号
一、行/列结构变更信号
这些信号用于通知视图模型的行或列结构发生变化(插入、删除、移动),需成对使用,分别在操作 开始前 和 完成后 触发。
1. 插入列
void columnsAboutToBeInserted(const QModelIndex &parent, int first, int last)
void columnsInserted(const QModelIndex &parent, int first, int last)
- 参数:
parent
: 插入列的父项索引。first
和last
: 插入列的起始和结束位置(闭区间,如插入 1 列时first == last
)。
- 触发时机:
columnsAboutToBeInserted
: 在调用beginInsertColumns()
后立即触发。columnsInserted
: 在调用endInsertColumns()
后触发。
2. 移动列
void columnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
const QModelIndex &destinationParent, int destinationColumn)
void columnsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
const QModelIndex &destinationParent, int destinationColumn)
- 参数:
sourceParent
: 源列的父项索引。sourceStart
和sourceEnd
: 要移动的列范围。destinationParent
: 目标父项索引。destinationColumn
: 目标列位置(插入到此列之前)。
- 触发时机:
columnsAboutToBeMoved
: 在调用beginMoveColumns()
后触发。columnsMoved
: 在调用endMoveColumns()
后触发。
3. 删除列
void columnsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
void columnsRemoved(const QModelIndex &parent, int first, int last)
- 参数:
parent
: 删除列的父项索引。first
和last
: 删除的列范围。
- 触发时机:
columnsAboutToBeRemoved
: 在调用beginRemoveColumns()
后触发。columnsRemoved
: 在调用endRemoveColumns()
后触发。
4. 插入行
void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
void rowsInserted(const QModelIndex &parent, int first, int last)
- 参数 和 触发时机: 与插入列类似,但操作对象为行。
5. 移动行
void rowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
const QModelIndex &destinationParent, int destinationRow)
void rowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
const QModelIndex &destinationParent, int destinationRow)
- 参数 和 触发时机: 与移动列类似,但操作对象为行。
6. 删除行
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
void rowsRemoved(const QModelIndex &parent, int first, int last)
- 参数 和 触发时机: 与删除列类似,但操作对象为行。
二、数据内容变更信号
7. 数据项内容变化
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
const QList<int> &roles = QList<int>())
- 作用: 通知视图一个矩形区域内的数据项内容发生变化。
- 参数:
topLeft
和bottomRight
: 定义受影响的区域(左上角和右下角索引)。roles
: 发生变化的角色列表(如Qt::DisplayRole
、Qt::EditRole
),默认为空表示所有角色。
- 触发时机:
- 调用
setData()
成功修改数据后自动触发。 - 手动调用以批量通知数据变更。
- 调用
三、表头数据变更信号
8. 表头数据变化
void headerDataChanged(Qt::Orientation orientation, int first, int last)
- 作用: 通知视图水平(
Qt::Horizontal
)或垂直(Qt::Vertical
)表头的数据发生变化。 - 参数:
orientation
: 表头方向。first
和last
: 受影响的表头范围(闭区间)。
- 触发时机:
- 调用
setHeaderData()
成功修改表头后自动触发。
- 调用
四、布局变更信号
9. 布局即将变化
void layoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>(),
QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoLayoutChangeHint)
- 作用: 通知视图模型布局即将发生重大变化(如排序、行列顺序调整)。
- 参数:
parents
: 受影响的父项索引列表(通常为空表示根项)。hint
: 布局变化类型提示(如VerticalSortHint
表示垂直排序)。
- 触发时机:
- 在调用
sort()
或手动触发布局变化前触发。
- 在调用
10. 布局变化完成
void layoutChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>(),
QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoLayoutChangeHint)
- 作用: 通知视图布局变化已完成。
- 触发时机:
- 在布局变化操作完成后触发(如排序结束)。
五、模型重置信号
11. 模型即将重置
void modelAboutToBeReset()
- 作用: 通知视图模型数据即将完全重置(所有现有数据可能失效)。
- 触发时机:
- 在调用
beginResetModel()
后立即触发。
- 在调用
12. 模型重置完成
void modelReset()
- 作用: 通知视图模型数据已完全重置,需重新加载所有数据。
- 触发时机:
- 在调用
endResetModel()
后触发。
- 在调用
六、信号触发流程示例
插入行的典型流程:
- 调用
beginInsertRows(parent, start, end)
。 - 触发
rowsAboutToBeInserted(parent, start, end)
。 - 执行插入数据操作。
- 调用
endInsertRows()
。 - 触发
rowsInserted(parent, start, end)
。
数据修改的典型流程:
- 调用
setData(index, value)
。 - 若修改成功,自动触发
dataChanged(index, index, roles)
。
七、关键注意事项
-
信号与
begin/end
方法的对应
所有结构变更信号(如rowsAboutToBeInserted
)应通过beginInsertRows()
等方法自动触发,而非手动发射。 -
性能优化
- 使用
dataChanged
时尽量缩小范围(如传递精确的topLeft
和bottomRight
)。 - 批量操作时,合并多次
dataChanged
信号为一次。
- 使用
-
布局变化与持久索引
在layoutAboutToBeChanged
和layoutChanged
之间,需更新持久索引(通过changePersistentIndex
)。 -
模型重置的代价
modelReset
会强制视图完全刷新,应尽量避免频繁调用。
保护槽
virtual void resetInternalData()
用于重置模型的内部数据结构。它通常用于模型内部状态发生重大变化时(如数据源完全改变),但不会自动触发视图的刷新信号
作用
-
清理或重置内部数据
当模型的底层数据源发生 不可逆的结构性变化(如重新加载文件、切换数据源)时,调用此函数可重置模型内部状态(如缓存、索引映射等)。 -
优化性能
避免直接调用beginResetModel()
/endResetModel()
的全局刷新,允许模型局部或静默更新数据,适用于需要 延迟通知视图 的场景。 -
子类定制逻辑
子类可重写此函数,实现自定义的清理或初始化逻辑(如释放资源、重建索引)。
触发时机
- 手动调用:由开发者显式调用,通常在数据源变更后。
- 框架调用:某些 Qt 内部操作可能间接触发(如代理模型的数据源变更)。
与 beginResetModel()
/endResetModel()
的区别
函数/操作 | 作用 | 是否触发视图刷新信号 |
---|---|---|
resetInternalData() | 仅重置模型内部数据,不通知视图 | 否 |
beginResetModel() /endResetModel() | 重置数据并通知视图全局刷新 | 是(触发 modelAboutToBeReset 和 modelReset ) |
典型使用场景
-
数据源切换后静默重置
需要更新模型数据但暂时不通知视图(例如批量操作中间步骤)。void MyModel::loadNewData(const Data& newData) { // 1. 重置内部数据 resetInternalData(); // 2. 加载新数据到内部结构 m_data = newData; // 3. 手动触发视图刷新(可选) emit dataChanged(...); }
-
子类重写实现资源管理
void MyModel::resetInternalData() { // 释放旧数据内存 qDeleteAll(m_cachedItems); m_cachedItems.clear(); // 重建内部数据结构 initializeInternalData(); }
注意事项
-
不会自动通知视图
调用resetInternalData()
后,若需更新视图,需手动触发信号(如dataChanged()
或调用beginResetModel()
/endResetModel()
)。 -
谨慎处理持久索引
若模型外部持有QPersistentModelIndex
,重置内部数据可能导致索引失效,需配合changePersistentIndex()
更新。 -
避免与重置信号混用
若已调用beginResetModel()
,通常无需再调用resetInternalData()
,因重置操作本身已隐含清理逻辑。
示例:结合重置信号
void MyModel::reloadData() {
beginResetModel(); // 通知视图即将重置
resetInternalData(); // 清理内部数据
loadDataFromSource(); // 加载新数据
endResetModel(); // 通知视图重置完成
}
枚举类型
1. CheckIndexOption
枚举与 CheckIndexOptions
标志
作用
控制 checkIndex()
函数在验证模型索引时的行为,决定检查的严格程度和验证范围。这些选项用于确保索引的合法性,帮助调试和增强模型健壮性。
枚举值
值 | 描述 |
---|---|
NoOption | 无额外检查,仅进行基本验证。 |
IndexIsValid | 检查索引本身是否有效(如行/列是否在有效范围内)。 |
DoNotUseParent | 验证时忽略父索引(不检查父索引是否合法)。 |
ParentIsInvalid | 强制要求父索引必须是无效的(如 QModelIndex() )。 |
标志组合 CheckIndexOptions
- 可通过位运算组合多个
CheckIndexOption
值,例如:CheckIndexOptions options = CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid;
- 用于
checkIndex()
的参数,定义验证规则:bool isValid = model->checkIndex(index, options);
使用场景
- 调试时严格检查索引合法性:
model->checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid);
- 忽略父索引的快速验证:
model->checkIndex(index, CheckIndexOption::DoNotUseParent);
2. LayoutChangeHint
枚举
作用
提供布局变化的提示信息,帮助视图优化更新逻辑(如排序方向)。在调用 layoutAboutToBeChanged()
和 layoutChanged()
时传递此提示。
枚举值
值 | 描述 |
---|---|
NoLayoutChangeHint | 无特定提示,视图需完全重新布局。 |
VerticalSortHint | 布局变化由垂直方向(行)的排序引起,视图可能保留水平滚动位置。 |
HorizontalSortHint | 布局变化由水平方向(列)的排序引起,视图可能保留垂直滚动位置。 |
使用场景
- 排序后保留滚动位置:
// 触发垂直排序的布局变化 emit layoutAboutToBeChanged({}, QAbstractItemModel::VerticalSortHint); // ... 排序操作 ... emit layoutChanged({}, QAbstractItemModel::VerticalSortHint);
- 优化性能:
视图可根据提示仅刷新部分区域(如保留未变化的方向的滚动位置)。
关键区别与联系
分类 | 目的 | 典型应用场景 |
---|---|---|
CheckIndexOptions | 控制索引验证的严格性 | 调试、确保模型操作的索引合法性 |
LayoutChangeHint | 优化布局变化的视图更新行为 | 排序后保留滚动位置、提升性能 |
示例代码
使用 CheckIndexOptions
QModelIndex index = model->index(0, 0);
if (model->checkIndex(index, CheckIndexOption::IndexIsValid)) {
// 索引有效,进行操作
model->setData(index, "New Value");
}
使用 LayoutChangeHint
// 触发垂直排序布局变化
emit layoutAboutToBeChanged({}, QAbstractItemModel::VerticalSortHint);
// 执行排序操作
sortData(Qt::AscendingOrder);
// 更新持久索引(如果需要)
updatePersistentIndexes();
// 通知布局变化完成
emit layoutChanged({}, QAbstractItemModel::VerticalSortHint);
注意事项
-
CheckIndexOptions
- 主要用于调试和内部验证,生产环境中过度使用可能影响性能。
- Qt 6.0 引入,旧版本不可用。
-
LayoutChangeHint
- 提示是可选的,视图可能忽略不支持的值。
- 与
changePersistentIndex()
配合使用,确保外部持有的索引正确更新。
其他枚举类型
enum Qt::ItemFlag/flags Qt::ItemFlags
Qt::NoItemFlags 0 它没有设置任何属性。
Qt::ItemIsSelectable 1 它可以被选中。
Qt::ItemIsEditable 2 可以进行编辑。
Qt::ItemIsDragEnabled 4 可以进行拖动。
Qt::ItemIsDropEnabled 8 可以用作拖放目标。
Qt::ItemIsUserCheckable 16 用户可以勾选或取消勾选。
Qt::ItemIsEnabled 32 用户可以与项目交互。
Qt::ItemIsAutoTristate 64 项目的状态取决于其子项的状态。这使 QTreeWidget 中父项的状态自动管理成为可能(如果所有子项都被选中,则选中;如果所有子项都被取消选中,则取消选中;如果只有一些子项被选中,则部分选中)。
Qt::ItemNeverHasChildren 128 该项从不具有子项。这仅用于优化目的。
Qt::ItemIsUserTristate 256 用户可以循环通过三个独立的状态。此值是在 Qt 5.5 中添加的。
模型的选择
对于列表类型的自定义模型继承QAbstractListModel
对于表格类型的自定义模型继承QAbstractTableModel
对于树形自定义模型继承QAbstractItemModel
示例
实现QStringListModel
效果
PixPin_2025-05-19_17-00-44
代码
main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return QApplication::exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "MyStringListModel.h"
#include <QModelIndex>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
bool addItem(const QString& item);
private:
Ui::MainWindow *ui;
MyStringListModel *m_model;
QModelIndex m_index{};
};
#endif //MAINWINDOW_H
mainwindow.cpp
//
// Created by 22257 on 2025/5/18.
//
// You may need to build the project (run Qt uic code generator) to get "ui_MainWindow.h" resolved
#include "mainwindow.h"
#include "ui_MainWindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent), ui(new Ui::MainWindow)
, m_model(new MyStringListModel(this))
{
ui->setupUi(this);
m_model->setStringList(QStringList() << "Hello" << "World" );
ui->listView->setModel(m_model);
connect(ui->addBtn,&QPushButton::clicked,this,[this](){
auto isAddItem= addItem("newItem");
if(isAddItem)
{
qDebug()<<"addItem 成功";
}
else
{
qDebug()<<"addItem 失败";
}
});
connect(ui->listView,&QListView::clicked,this,[this](const QModelIndex &index){
m_index=index;
qDebug()<<m_model->sibling(1,0,m_index);
});
connect(ui->clear,&QPushButton::clicked,this,[this](){
m_model->clearItemData(m_index);
});
connect(ui->remove,&QPushButton::clicked,this,[this](){
m_model->removeRows(m_index.row(),1);
});
connect(ui->move,&QPushButton::clicked,this,[this](){
m_model->moveRows(QModelIndex(),0,3,QModelIndex(),5);
});
connect(ui->sort,&QPushButton::clicked,this,[this](){
m_model->sort(0,Qt::AscendingOrder);
});
}
MainWindow::~MainWindow() {
delete ui;
}
bool MainWindow::addItem(const QString &item) {
//插入空行
auto isInsertNewLine= m_model->insertRows(m_model->rowCount(),1);
if(!isInsertNewLine)
{
qDebug()<<"addItem 插入空行失败";
return false;
}
//添加新数据
auto isAddData= m_model->setData(m_model->index(m_model->rowCount()-1,0),item,Qt::EditRole);
if(!isAddData)
{
qDebug()<<"addItem 添加新数据失败";
return false;
}
return true;
}
MyStringListModel.h
#ifndef MYSTRINGLISTMODEL_H
#define MYSTRINGLISTMODEL_H
#include <QStringList>
#include <QObject>
#include <QHash>
#include <QList>
#include <QAbstractItemModel>
class MyStringListModel : public QAbstractItemModel {
Q_OBJECT
public:
explicit MyStringListModel(QObject *parent = nullptr);
explicit MyStringListModel(const QStringList &strings, QObject *parent = nullptr);
~MyStringListModel()override ;
//作用是返回模型中的行数。
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
//作用是返回模型中的列数。
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
//作用是返回模型中指定索引位置的父索引。
QModelIndex parent(const QModelIndex &index) const override;
//作用是返回模型中指定索引位置的子索引。
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex sibling(int row, int column, const QModelIndex &idx) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
bool clearItemData(const QModelIndex &index) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override;
QMap<int, QVariant> itemData(const QModelIndex &index) const override;
bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles) override;
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
QStringList stringList() const;
void setStringList(const QStringList &strings);
Qt::DropActions supportedDropActions() const override;
private:
QStringList lst;
};
#endif
MyStringListModel.cpp
#include "MyStringListModel.h"
MyStringListModel::MyStringListModel(const QStringList &strings, QObject *parent)
: QAbstractItemModel(parent) ,lst(strings) {}
int MyStringListModel::rowCount(const QModelIndex &parent) const {
//如果parent不是无效索引, 则返回0
if (parent.isValid()) {
return 0;
}
return lst.count();// 返回行数
}
int MyStringListModel::columnCount(const QModelIndex &parent) const {
//如果parent不是无效索引, 则返回0
if (parent.isValid()) {
return 0;
}
return 1; // 只有一列
}
QModelIndex MyStringListModel::parent(const QModelIndex &index) const {
return {}; // 没有父节点, 返回无效索引
}
QModelIndex MyStringListModel::index(int row, int column, const QModelIndex &parent) const {
//判断row和column是否有效
if ((row<0||row>=lst.count())||(column<0||column>=1)) {
return {}; // 无效索引返回无效索引
}
//如果parent不是无效索引, 则返回无效索引
if (parent.isValid()) {
return {};
}
//返回对应行的索引
return createIndex(row, column);
}
QModelIndex MyStringListModel::sibling(int row, int column, const QModelIndex &idx) const {
if (!idx.isValid()||row<0||row>=lst.count()||column!=0) {
return {}; // 无效索引返回无效索引
}
return createIndex(row, column);
}
QVariant MyStringListModel::data(const QModelIndex &index, int role) const {
//判断索引是否有效
//index.row()<0||index.row()>=lst.count()
if (!index.isValid()) {
return {}; // 无效索引返回空值
}
//判断role是否有效
if (role == Qt::DisplayRole||role==Qt::EditRole) {
return lst[index.row()]; // 返回对应行的字符串
}
else //如果role无效, 返回空值
return {};
}
bool MyStringListModel::setData(const QModelIndex &index, const QVariant &value, int role) {
//判断索引是否有效
//index.row()<0||index.row()>=lst.count()
if (!index.isValid()) {
return false; // 无效索引返回false
}
//判断是否设置数据成功
bool success{false};
//判断role是否有效
if (role == Qt::EditRole||role==Qt::DisplayRole) {
//如果value是无效的说明正在进行清除操作(clearItemData),则清空对应行的字符串
if (!value.isValid()) {
lst[index.row()]={};
}
else //否则设置对应行的字符串
{
lst[index.row()]=value.toString(); // 设置对应行的字符串
Q_EMIT dataChanged(index, index, { Qt::EditRole,Qt::DisplayRole}); // 触发数据改变信号
}
success=true;
}
return success;
}
bool MyStringListModel::clearItemData(const QModelIndex &index) {
// 1. 检查索引有效性
if (!index.isValid()) {
return false;
}
// 2. 定义支持清除的角色列表(需与 itemData/setItemData 一致)
const QList<int> clearableRoles = {
Qt::DisplayRole,
Qt::EditRole,
//Qt::DecorationRole,
//Qt::ToolTipRole
// 添加其他支持清除的角色...
};
bool allSuccess = true;
QList<int> clearedRoles;
// 3. 遍历角色并清除数据
for (const int &role : clearableRoles) {
// 调用 setData 将角色数据设为无效
if (setData(index, QVariant(), role)) {
clearedRoles.append(role);
} else {
allSuccess = false; // 存在角色清除失败
}
}
// 4. 统一触发数据变更信号
if (!clearedRoles.isEmpty()) {
Q_EMIT dataChanged(index, index, clearedRoles);
}
return allSuccess;
}
Qt::ItemFlags MyStringListModel::flags(const QModelIndex &index) const {
//判断索引是否有效
if (!index.isValid())) {
//无效则调用父类的flags方法, 返回默认的flags+Qt::ItemIsDropEnabled标志, 表示该项目可放置
return QAbstractItemModel::flags(index)| Qt::ItemIsDropEnabled ;
}
//有效返回默认的flags, 并添加Qt::ItemIsEditable和Qt::ItemIsDragEnabled和Qt::ItemIsDropEnabled标志
//表示该项目可编辑, 可拖动和可放置
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
bool MyStringListModel::insertRows( int row, int count, const QModelIndex &parent) {
//判断parent是否有效
if (parent.isValid()) {
return false; // 无效索引返回false
}
//判断行号和数是否有效,因为是插入所以是可以等于行数的
if ((row<0||row>lst.count())||(count<1)) {
return false; // 无效索引返回false
}
beginInsertRows(parent, row, row + count - 1); // 开始插入行, 会触发数据改变信号
for (int i = 0; i < count; i++) {
lst.insert(row, QString{}); // 在指定位置插入空字符串
}
endInsertRows(); // 结束插入行, 会触发数据改变信号
return true;
}
bool MyStringListModel::removeRows(int row, int count, const QModelIndex &parent) {
//判断parent是否有效
if (parent.isValid()) {
return false; // 无效索引返回false
}
//判断行号和数是否有效
if ((row<0||row>=lst.count())||(count<1)) {
return false; // 无效索引返回false
}
beginRemoveRows(parent, row, row + count - 1);
for (int i = 0; i < count; i++) {
lst.removeAt(row); // 删除指定位置的字符串
}
endRemoveRows();
return true;
}
bool MyStringListModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
const QModelIndex &destinationParent, int destinationChild) {
// 平面模型中,父索引应该无效
if (sourceParent.isValid() || destinationParent.isValid())
return false;
// 验证参数
if (sourceRow < 0 || sourceRow >=rowCount() ||
destinationChild < 0 || destinationChild > rowCount() ||
count <= 0)
return false;
// 如果源和目标相同或目标是源内部,则不需要移动
if (sourceRow == destinationChild ||
//例如 2 3 4, 4是目标位置但他是移动的3行之中一行
(sourceRow < destinationChild && destinationChild < sourceRow + count))
return false;
// 开始移动操作
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1,
destinationParent, destinationChild);
for (int i = 0; i < count; ++i) {
// 移动元素
lst.move(sourceRow , destinationChild);
}
// 结束移动操作
endMoveRows();
return true;
}
QMap<int, QVariant> MyStringListModel::itemData(const QModelIndex &index) const {
QMap<int, QVariant> rolesData{};
// 1. 检查索引有效性
if (!index.isValid()) {
return rolesData;
}
// 2. 定义模型支持的角色列表,根据具体情况添加,例如QStringListModel只需DisplayRole和EditRole
const QList<int> supportedRoles = {
Qt::DisplayRole,
Qt::EditRole,
//Qt::DecorationRole,
//Qt::ToolTipRole
// 添加其他支持的角色...
};
// 3. 遍历角色并收集数据
for (int role : supportedRoles) {
if (QVariant value = data(index, role); value.isValid()) {
rolesData.insert(role, value);
}
}
// 4. 返回包含所有支持角色的数据的映射
return rolesData;
}
bool MyStringListModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles) {
// 1. 检查索引和输入有效性
if (!index.isValid() || roles.isEmpty()) {
return false;
}
bool allSuccess = true;
QList<int> changedRoles;
// 2. 遍历角色并设置数据
for (auto it = roles.constBegin(); it != roles.constEnd(); ++it) {
const int role = it.key();
// 调用 setData 设置单个角色
if (const QVariant &value = it.value(); setData(index, value, role)) {
changedRoles.append(role); // 记录成功设置的角色
} else {
allSuccess = false; // 任一角色设置失败则整体失败
}
}
return allSuccess;
}
void MyStringListModel::sort(int column, Qt::SortOrder order) {
// 防止不必要的排序
if (rowCount() <= 1)
return;
// 通知视图模型即将改变
Q_EMIT layoutAboutToBeChanged();
// 保存持久化索引,以便在排序后更新它们
QModelIndexList persistentIndices = persistentIndexList();
QList<QPair<int, QVariant>> oldRows;
// 保存排序前的行号和数据
for (int row = 0; row < rowCount(); ++row) {
QModelIndex index = this->index(row, column);
oldRows.append(qMakePair(row, data(index, Qt::DisplayRole)));
}
// 根据指定列的数据排序
// 大小写不敏感的比较函数(升序)
auto caseInsensitiveLessThan = [](const QVariant &v1, const QVariant &v2) -> bool {
// 如果是字符串类型,使用大小写不敏感比较
if (v1.typeId() == QMetaType::QString && v2.typeId() == QMetaType::QString) {
return v1.toString().compare(v2.toString(), Qt::CaseInsensitive) < 0;
}
// 对于非字符串类型,尝试转换为同一类型后比较
if (v1.canConvert<QString>() && v2.canConvert<QString>()) {
return v1.toString() < v2.toString();
}
return false; // 或者根据需求返回其他默认值
};
// 大小写不敏感的比较函数(降序)
auto caseInsensitiveGreaterThan = [](const QVariant &v1, const QVariant &v2) -> bool {
if (v1.typeId() == QMetaType::QString && v2.typeId() == QMetaType::QString) {
return v1.toString().compare(v2.toString(), Qt::CaseInsensitive) > 0;
}
// 对于非字符串类型,尝试转换为同一类型后比较
if (v1.canConvert<QString>() && v2.canConvert<QString>()) {
return v1.toString() > v2.toString();
}
return false; // 或者根据需求返回其他默认值
};
// 根据排序顺序选择比较函数
if (order == Qt::AscendingOrder) {
std::sort(oldRows.begin(), oldRows.end(),
[&caseInsensitiveLessThan](const QPair<int, QVariant> &a, const QPair<int, QVariant> &b) {
return caseInsensitiveLessThan(a.second, b.second);
});
} else {
std::sort(oldRows.begin(), oldRows.end(),
[&caseInsensitiveGreaterThan](const QPair<int, QVariant> &a, const QPair<int, QVariant> &b) {
return caseInsensitiveGreaterThan(a.second, b.second);
});
}
// 创建旧行号到新行号的映射
QHash<int, int> rowMapping;
for (int newRow = 0; newRow < oldRows.size(); ++newRow) {
rowMapping[oldRows[newRow].first] = newRow;
}
// 重新排列内部数据
QStringList newData(lst.size());
for (int oldRow = 0,newRow=0; oldRow < lst.size(); ++oldRow) {
newRow = rowMapping[oldRow];
newData[newRow] = lst[oldRow];
}
lst = newData;
// 更新持久化索引
QModelIndexList newPersistentIndices;
for (const QModelIndex &oldIndex : persistentIndices) {
if (oldIndex.isValid() && oldIndex.row() < rowCount()) {
int newRow = rowMapping.value(oldIndex.row(), -1);
newPersistentIndices << ((newRow >= 0) ?
createIndex(newRow, oldIndex.column(), oldIndex.internalPointer())
:QModelIndex());
} else {
newPersistentIndices << QModelIndex();
}
}
// 更新持久化索引并通知视图布局已更改
changePersistentIndexList(persistentIndices, newPersistentIndices);
Q_EMIT layoutChanged();
}
QStringList MyStringListModel::stringList() const {
return lst;
}
void MyStringListModel::setStringList(const QStringList &strings) {
beginResetModel();// 开始重置模型, 会触发数据改变信号
lst = strings;// 重新设置数据
endResetModel();// 结束重置模型, 会触发数据改变信号
}
Qt::DropActions MyStringListModel::supportedDropActions() const {
return QAbstractItemModel::supportedDropActions();
}
MyStringListModel::MyStringListModel(QObject *parent) : QAbstractItemModel(parent), lst() {
}
MyStringListModel::~MyStringListModel() = default;
CMakeLists.txt
cmake_minimum_required(VERSION 3.31)
project(MyStringListModel)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_PREFIX_PATH "D:\\software\\Qt\\6.8.0\\mingw_64")
find_package(Qt6 COMPONENTS
Core
Gui
Widgets
REQUIRED)
add_executable(MyStringListModel main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
MyStringListModel.cpp
MyStringListModel.h
)
target_link_libraries(MyStringListModel
Qt::Core
Qt::Gui
Qt::Widgets
)
if (WIN32 AND NOT DEFINED CMAKE_TOOLCHAIN_FILE)
set(DEBUG_SUFFIX)
if (MSVC AND CMAKE_BUILD_TYPE MATCHES "Debug")
set(DEBUG_SUFFIX "d")
endif ()
set(QT_INSTALL_PATH "${CMAKE_PREFIX_PATH}")
if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
endif ()
endif ()
if (EXISTS "${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/plugins/platforms/")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll"
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/plugins/platforms/")
endif ()
foreach (QT_LIB Core Gui Widgets)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${QT_INSTALL_PATH}/bin/Qt6${QT_LIB}${DEBUG_SUFFIX}.dll"
"$<TARGET_FILE_DIR:${PROJECT_NAME}>")
endforeach (QT_LIB)
endif ()