文章目录
-
- 效果图
- 一、整体架构设计:基于MVC模式的分层实现
- 二、核心组件解析
-
- [1. 自定义委托:`GeneralDelegate`实现单元格交互](#1. 自定义委托:
GeneralDelegate
实现单元格交互) - [2. 分页导航控件:`PageNavigator`的状态管理](#2. 分页导航控件:
PageNavigator
的状态管理) - [3. 表格页面封装:`QTablePages`的整合能力](#3. 表格页面封装:
QTablePages
的整合能力) - [4. 远程数据模型:`RemoteTableModel`的数据处理(核心)](#4. 远程数据模型:
RemoteTableModel
的数据处理(核心))
- [1. 自定义委托:`GeneralDelegate`实现单元格交互](#1. 自定义委托:
- 三、实践经验与优化方向
- 四、组件协作关系图
- 五、总结
-
上篇文章中使用到了
QSqlTableModel
来管理与处理数据库的操作,但这个模型有一个很大的局限,就是它只能处理本地数据库,不能处理远程数据库,所以便开发了下述远程模式的model。
效果图

一、整体架构设计:基于MVC模式的分层实现
本次分享的表格系统严格遵循Qt的MVC(Model-View-Controller)设计模式,各组件职责清晰:
- 模型(Model) :
RemoteTableModel
负责数据的获取、存储和处理,与后端数据源交互 - 视图(View) :
ToolTipTableView
负责数据的可视化展示 - 委托(Delegate) :
GeneralDelegate
处理单元格的自定义渲染和用户交互 - 控制器(Controller) :
QTablePages
整合上述组件,协调数据流转与用户操作
此外,PageNavigator
作为独立的分页控件,负责页码管理;LogManagement
则承担数据模型的统一初始化与管理。这种分层设计让各组件可独立维护、灵活复用。
二、核心组件解析
1. 自定义委托:GeneralDelegate
实现单元格交互
表格中常需要在单元格中嵌入按钮等交互元素,GeneralDelegate
通过重写Qt委托的关键方法实现这一功能:
cpp
// 重写paint方法绘制删除按钮
void GeneralDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
int currentColumn = index.column();
int columnCount = index.model()->columnCount();
bool isLastColumn = (currentColumn == columnCount - 1);
if (isLastColumn) {
QRect buttonRect = option.rect.adjusted(2, 2, -2, -2);
QStyleOptionButton buttonOption;
buttonOption.rect = buttonRect;
buttonOption.state |= QStyle::State_Enabled;
buttonOption.palette.setBrush(QPalette::Button, QColor(Qt::red));
buttonOption.text = "删除";
painter->save();
painter->setClipRect(buttonRect);
QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter);
painter->restore();
} else {
// 普通单元格渲染
QStyleOptionViewItem options = option;
initStyleOption(&options, index);
options.displayAlignment = Qt::AlignCenter;
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);
}
}
同时,通过editorEvent
处理按钮点击事件,并通过信号将交互传递给上层:
cpp
bool GeneralDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
if (event->type() == QEvent::MouseButtonRelease && isLastColumn) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
QRect buttonRect = option.rect.adjusted(2, 2, -2, -2);
if (buttonRect.contains(mouseEvent->pos())) {
emit deleteRequested(index); // 发送删除信号
return true;
}
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
这种实现方式既保持了Qt委托机制的灵活性,又能自定义复杂的单元格交互。
2. 分页导航控件:PageNavigator
的状态管理
分页是大数据表格的必备功能
3. 表格页面封装:QTablePages
的整合能力
QTablePages
作为上层封装类,将表格视图、数据模型和分页控件有机结合:
cpp
void QTablePages::InitTableForm(RemoteTableModel *dataModel, int pageLines)
{
m_dataModel = dataModel;
m_pageLines = pageLines;
// 设置表格视图
m_tableView->setModel(m_dataModel);
m_tableView->setItemDelegate(new GeneralDelegate(this));
// 连接分页信号
connect(m_pageNavBar, &PageNavigator::SigCurrentPageChanged,
this, &QTablePages::OnPageChanged);
// 连接删除信号
connect(m_tableView->itemDelegate(), &GeneralDelegate::deleteRequested,
this, &QTablePages::onDeleteButtonClicked);
}
通过refreshData
方法实现带筛选条件的刷新,支持日期范围、关键词等多条件筛选,极大提升了表格的实用性。
4. 远程数据模型:RemoteTableModel
的数据处理(核心)
RemoteTableModel
作为数据核心,实现了与远程数据源的交互和本地数据管理:
-
异步数据加载 :通过
loadPage
方法异步获取数据,避免UI阻塞cppvoid RemoteTableModel::loadPage(int page) { if (m_loading) return; m_loading = true; beginResetModel(); m_records.clear(); m_client->ExecuteQuery( m_filter.toStdString(), [this](const std::vector<Record> &records) { // 数据处理逻辑 beginResetModel(); m_records = newData; endResetModel(); m_loading = false; emit loadFinished(newData); }); }
-
数据标准化 :在
handleDataLoaded
中统一处理数据格式,确保视图展示一致性cppfor (const auto &record : records) { QVariantMap normalizedRecord; for (auto it = record.begin(); it != record.end(); ++it) { QString englishKey = it.key().toLower(); // 统一键名格式 if (englishKey == "total_count") { m_totalCount = it.value().toInt(); // 提取总记录数 continue; } if (COLUMN_MAPPING.contains(englishKey)) { normalizedRecord[englishKey] = it.value(); } } m_records.push_back(normalizedRecord); }
-
完整CRUD支持 :提供
insertRow
、updateRow
、removeRow
等方法,封装数据操作逻辑 -
远程数据操作最核心的挑战是避免 UI 阻塞,RemoteTableModel通过异步回调 + 信号槽机制解决这一问题:
cpp
void RemoteTableModel::loadPage(int page)
{
if (m_loading) return; // 防止重复请求
m_loading = true;
beginResetModel(); // 通知视图即将重置数据
m_records.clear(); // 清空本地缓存
// 异步执行远程查询
m_client->ExecuteQuery(
m_filter.toStdString(), // 传递筛选条件
[this](const std::vector<Record> &records) { // 查询结果回调
// 转换远程数据为本地QVariantMap格式
QVector<QVariantMap> newData;
for (const auto &record : records) {
QVariantMap vm;
auto dataMap = record.at("data");
for (auto it = dataMap.begin(); it != dataMap.end(); ++it) {
vm[QString::fromStdString(it->first)] = QString::fromStdString(it->second);
}
newData.append(vm);
}
beginResetModel(); // 通知视图数据开始更新
m_records = newData; // 更新本地缓存
endResetModel(); // 通知视图数据更新完成
m_loading = false;
emit loadFinished(newData); // 触发数据处理流程
});
}
- 状态锁控制:m_loading标记防止并发请求,确保数据一致性
- 视图通知机制:beginResetModel()和endResetModel()成对调用,告知视图重新加载数据,避免界面错乱
三、实践经验与优化方向
-
性能优化:
- 采用异步加载避免UI卡顿
- 数据分页减少一次性加载压力
- 模型重置时使用
beginResetModel
和endResetModel
确保视图高效更新
-
可扩展性设计:
LogManagement
中通过统一方法初始化不同业务模型,便于新增表格类型- 委托类与业务逻辑分离,可根据需求定制不同单元格样式
-
用户体验提升:
- 分页按钮状态动态反馈
- 单元格tooltip提示完整信息
- 支持数据导出CSV功能
-
未来优化方向:
- 增加数据缓存机制减少重复请求
- 实现表格列的动态显示/隐藏
- 加入单元格编辑功能的统一处理
四、组件协作关系图
plaintext
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ LogManagement │──┬──▶│ RemoteTableModel│◀────▶│DBClientThreadHandler│
└─────────────────┘ │ └────────┬────────┘ └─────────────────┘
│ │
│ ▼
│ ┌─────────────────┐
└──▶│ QTablePages │
└────────┬────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ToolTipTableView│ │PageNavigator│ │GeneralDelegate│
└─────────────┘ └─────────────┘ └─────────────┘
五、总结
- 本文介绍的表格组件套装通过合理的架构设计和组件封装,实现了数据展示、分页导航、自定义交互等核心功能。基于Qt的MVC模式和信号槽机制,各组件既相互协作又保持独立,既保证了功能的完整性,又具备良好的可维护性和可扩展性。
- 代码中grpc通信模块没做展示,因为grpc通信模块比较简单,且不是本文的重点,所以没做展示。
- 核心代码