当前位置: 首页 > news >正文

qt模型视图架构使用时需要注意什么

使用Qt的模型/视图(Model/View)架构时,需要注意多个方面,从内存管理到性能优化,再到正确的信号发射。


🧠 一、核心原则:模型与视图分离

切记:模型负责管理数据,视图负责显示数据。不要在视图的委托(Delegate)或操作数据的代码中直接操作视图(如 QListViewQTableView 本身),而应始终通过模型接口进行数据操作。

反例(错误)

// 错误:试图直接修改视图项
listView->item(0)->setText("New Text");

正例(正确)

// 正确:通过模型修改数据
QModelIndex index = model->index(0, 0); // 获取模型索引
model->setData(index, "New Text");     // 通过模型接口修改数据
// 模型内部会负责发射 dataChanged() 信号,视图会自动更新

📊 二、模型更新的信号机制(重中之重)

这是最容易出错的地方。任何对模型数据的修改都必须通过发射合适的信号来通知视图

操作类型需要调用的方法或信号说明
数据内容改变emit dataChanged(topLeft, bottomRight)修改现有项的数据内容时使用。需指定改变区域的范围。
插入行/列beginInsertRows(parent, first, last)
endInsertRows()
必须成对调用。在两者之间执行实际的数据插入操作。
移除行/列beginRemoveRows(parent, first, last)
endRemoveRows()
必须成对调用。在两者之间执行实际的数据移除操作。
模型布局巨变beginResetModel()
endResetModel()
必须成对调用。会重置视图状态(如选择、滚动位置),慎用
移动行beginMoveRows(sourceParent, srcFirst, srcLast, destinationParent, destinationRow)
endMoveRows()
必须成对调用。用于移动行到新位置。

示例:插入行

// 正确插入行的流程
void MyModel::insertNewRow(int row, const Data &data) {beginInsertRows(QModelIndex(), row, row); // 1. 通知视图:行即将插入m_data.insert(row, data);                 // 2. 实际修改底层数据endInsertRows();                          // 3. 通知视图:插入完成
}
问题1:qt修改了模型的数据项,而这个数据项又影响排序,怎么通知视图更新

在Qt中,当你修改了模型的数据项,并且这个修改会影响模型的排序顺序时,通知视图更新的方法需要特别注意。你不能简单地使用 dataChanged() 信号,因为它只更新显示,不会改变项目的位置。修改影响排序的数据项后,你需要:

  1. 更新数据项的值

  2. 将该项移动到新的正确位置(因为排序顺序变了)

  3. 通知视图这两项变化

问题1解决方案
🔧 方法一:先移除再插入(最通用)

这是最直接的方法,适用于任何类型的模型。

// 假设修改了第 row 行的数据,该数据影响排序
void MyModel::updateItemAndReorder(int row, const QVariant& newValue)
{// 1. 保存要修改的数据项MyDataItem item = m_dataList.takeAt(row); // 从数据结构中移除该项item.setValue(newValue); // 修改值// 2. 通知视图移除了该行beginRemoveRows(QModelIndex(), row, row);// takeAt() 已经在上面执行了实际的移除操作endRemoveRows();// 3. 找到新的插入位置(根据你的排序规则)int newRow = findCorrectInsertionIndex(item);// 4. 通知视图将在 newRow 位置插入行beginInsertRows(QModelIndex(), newRow, newRow);m_dataList.insert(newRow, item); // 在正确位置插入endInsertRows();
}// 辅助函数:找到正确的插入位置
int MyModel::findCorrectInsertionIndex(const MyDataItem& item)
{// 根据你的排序规则实现查找逻辑for (int i = 0; i < m_dataList.size(); ++i) {if (shouldItemComeBefore(item, m_dataList.at(i))) {return i;}}return m_dataList.size(); // 如果应该放在最后
}
🔧 方法二:使用 layoutChanged() 信号(较简单)

对于 QAbstractItemModel,可以使用 layoutChanged 信号来通知视图布局(包括排序顺序)发生了变化。

void MyModel::updateItemAndNotify(int row, const QVariant& newValue)
{// 1. 修改数据m_dataList[row].setValue(newValue);// 2. 对整个模型重新排序std::sort(m_dataList.begin(), m_dataList.end(), mySortFunction);// 3. 发射 layoutChanged 信号emit layoutChanged({}, QAbstractItemModel::VerticalSortHint);// 或者使用以下方式,效果类似:// QModelIndexList persisted;// emit layoutAboutToBeChanged(persisted);// emit layoutChanged(persisted);
}
🔧 方法三:使用代理模型(最优雅)

如果使用 QSortFilterProxyModel,事情会变得非常简单:

// 在你的主窗口或控制器中
void MainWindow::updateItem(int row, const QVariant& newValue)
{// 1. 获取源模型索引QModelIndex proxyIndex = tableView->currentIndex();QModelIndex sourceIndex = sortFilterProxyModel->mapToSource(proxyIndex);// 2. 修改源模型的数据sourceModel->setData(sourceIndex, newValue);// 3. 代理模型会自动检测到数据变化并重新排序!// 不需要任何额外操作
}

使用代理模型的完整示例:

// 设置代理模型
QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(mySourceModel); // 你的原始模型
proxyModel->setSortRole(MyCustomSortRole); // 设置用于排序的角色
proxyModel->sort(0, Qt::DescendingOrder); // 按第0列降序排序tableView->setModel(proxyModel); // 视图使用代理模型
🔧 方法四:手动移动项目位置

对于 QAbstractListModel,可以使用 beginMoveRows() 和 endMoveRows()

void MyModel::updateAndMoveItem(int row, const QVariant& newValue)
{// 1. 修改数据m_dataList[row].setValue(newValue);// 2. 找出新的正确位置int newRow = findCorrectPositionForRow(row);if (newRow != row) {// 3. 通知视图要移动行beginMoveRows(QModelIndex(), row, row, QModelIndex(), newRow);m_dataList.move(row, newRow); // 移动数据endMoveRows();} else {// 如果位置没变,只需更新显示QModelIndex changedIndex = createIndex(row, 0);emit dataChanged(changedIndex, changedIndex);}
}

📊 方案对比
方法适用场景优点缺点
移除再插入所有模型类型通用性强需要手动实现
layoutChanged简单应用实现简单性能较差
代理模型新项目或可重构项目自动排序,代码简洁需要调整架构
手动移动列表模型精确控制实现复杂
💡 推荐方案
  1. 新建项目:强烈推荐使用 方法三(代理模型),这是最符合Qt设计理念的方式。

  2. 现有项目:如果已有复杂模型,使用 方法一(移除再插入)

  3. 简单需求:如果数据量小,可以使用 方法二(layoutChanged)

对于大多数情况,代理模型(QSortFilterProxyModel)是最佳选择,因为它将排序逻辑与数据模型分离,让代码更加清晰和可维护。


⚡ 三、性能优化

  1. 避免过度使用 resetModel()
    beginResetModel() / endResetModel() 会导致视图完全重建,丢失所有状态(选中项、滚动位置等)。优先使用增量更新信号(如 rowsInserteddataChanged)。

  2. 批量操作
    批量修改数据时,应发射一个覆盖所有更改区域的 dataChanged() 信号,而不是为每个单元格都发射一次。

    // 好:批量更新,发射一次信号
    emit dataChanged(index(0, 0), index(5, 3)); // 更新一个矩形区域// 不好:循环中多次发射信号,性能极差
    for (int r = 0; r < 6; ++r) {for (int c = 0; c < 4; ++c) {emit dataChanged(index(r, c), index(r, c));}
    }
  3. 实现 roleNames()
    如果在QML中使用模型,必须重写 roleNames() 函数,将角色枚举映射为字符串,否则QML无法通过角色名访问数据。

    QHash<int, QByteArray> MyModel::roleNames() const {QHash<int, QByteArray> roles;roles[NameRole] = "name";roles[EmailRole] = "email";return roles;
    }

🔗 四、代理模型(Proxy Model)的使用

代理模型(如 QSortFilterProxyModel)用于排序、过滤。注意

  • 视图应设置为代理模型,而不是源模型。

    QTableView view;
    QSortFilterProxyModel proxy;
    proxy.setSourceModel(&sourceModel); // 代理包装源模型
    view.setModel(&proxy);              // 视图设置代理模型
  • 从视图获取的索引属于代理模型,需要 mapToSource() 才能操作源模型。

    QModelIndex proxyIndex = view->currentIndex();
    QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);
    sourceModel->setData(sourceIndex, value); // 操作源模型

🎨 五、委托(Delegate)的注意事项

委托用于自定义项的渲染和编辑器。

  • 性能:在 paint() 中避免耗时的操作(如加载图片、复杂计算)。

  • 编辑器:创建编辑器时,应使用模型的当前数据初始化它。关闭编辑器时,必须通过模型提交数据。

  • 内存:确保正确管理编辑器的生命周期(通常由视图负责)。


🧩 六、线程安全

绝对禁止:在非GUI线程(工作线程)中直接修改模型,因为视图生活在GUI线程中。

正确做法

  1. 在线程中修改数据,然后通过信号槽(使用 Qt::QueuedConnection)通知主线程中的模型进行更新。

  2. 或者,使用 QMetaObject::invokeMethod 将更新请求排队到主线程执行。

// 在工作线程中
void WorkerThread::onDataReady(const Data &newData) {// 不能直接调用 model->insertRow(...);// 正确:通过信号槽排队到主线程emit requestAddData(newData); // 连接方式为 Qt::QueuedConnection
}// 在主线程的模型中
void MyModel::addDataSlot(const Data &data) {// 这里是主线程,可以安全修改模型beginInsertRows(QModelIndex(), rowCount(), rowCount());m_data.append(data);endInsertRows();
}

💎 总结:最佳实践清单

  1. 永远通过模型接口操作数据,而非直接操作视图项。

  2. 严格遵循信号发射规则:数据变则发 dataChanged,结构变则用 begin/end 系列函数。

  3. 优先增量更新,避免粗暴的 resetModel()

  4. 批量操作,减少信号发射次数。

  5. 在QML中使用模型,务必实现 roleNames()

  6. 使用代理模型时,注意索引的 映射(mapToSource/mapFromSource)

  7. 绝对禁止跨线程直接修改模型,必须通过线程间通信。

  8. 在委托中,保持 paint() 方法轻量高效

遵循这些注意事项,你将能构建出高效、稳定且可维护的Qt模型/视图应用程序。


文章转载自:

http://xCdTXG16.fgkrh.cn
http://Rdr8VTSM.fgkrh.cn
http://2RBVtAVA.fgkrh.cn
http://bVlImqhB.fgkrh.cn
http://mQpdSqHy.fgkrh.cn
http://CAdd0pli.fgkrh.cn
http://XUnUnMxS.fgkrh.cn
http://HXqRDNKh.fgkrh.cn
http://kVsrHSXs.fgkrh.cn
http://WrZ7QGyV.fgkrh.cn
http://9dnUFZMv.fgkrh.cn
http://lVXdExVk.fgkrh.cn
http://dLfJk4fg.fgkrh.cn
http://NNZu2bfL.fgkrh.cn
http://67u09FPE.fgkrh.cn
http://TETINwvq.fgkrh.cn
http://CZJ63HPs.fgkrh.cn
http://nS6i10aK.fgkrh.cn
http://8Jx7PU9L.fgkrh.cn
http://D6gpHW0M.fgkrh.cn
http://CFaZrnmT.fgkrh.cn
http://5lQUibql.fgkrh.cn
http://IABy5tLZ.fgkrh.cn
http://r54BpHdl.fgkrh.cn
http://NzIxUd3M.fgkrh.cn
http://MWjnQgOA.fgkrh.cn
http://dOJxbkRN.fgkrh.cn
http://1V3SwG2r.fgkrh.cn
http://GC10JKSm.fgkrh.cn
http://2UORKbLX.fgkrh.cn
http://www.dtcms.com/a/388003.html

相关文章:

  • webRTC golang 开发核心
  • UVa10603 Fill
  • 小说《灵渊纪元:数据重构天道》的深层解读与象征意义分析
  • Android Kotlin 实现微信分享功能
  • Git : 多人协作和企业级开发模型
  • Twitter/X 搜索headers x-client-transaction-id 参数
  • Node.js后端工程师需了解的前端技术:HTML5、JavaScript、CSS、工具(Axios、EJS、 Chart.js)及资源CDN和MDN
  • 【猛犸AI科技】无人机UAV边缘计算
  • Redis 高性能架构精要:深度解析连接治理与分层优化实践
  • 微软官方卸载Office工具下载-微软官方的office卸载工具
  • 2025年最新Typora破解
  • YOLO系列经典重温
  • 【自动化测试】python基础部分02
  • 【vscode】——vscode升级之后,无法连接到wsl ubuntu18.04
  • 如何解决 pip install 安装报错 ModuleNotFoundError: No module named ‘MySQLdb’ 问题
  • 雅菲奥朗SRE知识墙分享(八):『SRE事件管理的定义与实践』
  • UI 自动化测试中元素被遮挡无法点击的解决方案(Selenium + Python 实战)
  • 消除PCB电磁干扰的方法:从设计到制造的系统性解决方案
  • 图解算法java
  • Kotlin flow详解
  • Class1:Android Studio下载安装教程
  • windwos 下搭建OpenCV开发环境(基于Qt 5.14.2)
  • QSharedMemory + QSystemSemaphore实现进程间通讯的思路、关键点,并附一个完整可运行的Qt Demo(Qt Creator工程)
  • 使用python-fastApi框架开发一个学校宿舍管理系统-前后端分离项目
  • nblot BC260Y-CN ONENET oneJSON上云
  • 硬件驱动——I.MX6ULL裸机启动(6)(i2c相关设置)
  • 9.18 丑数|换根dp
  • QListWidget选择阻止问题解决方案
  • Qt 系统相关 - 多线程
  • 孔夫子旧书网开放平台接口实战:古籍图书检索与商铺数据集成方案