Qt表格组件封装与远程数据库连接:从数据展示到交互体验
文章目录
- 效果图
- 一、整体架构设计:基于MVC模式的分层实现
- 二、核心组件解析
- 1. 自定义委托:`GeneralDelegate`实现单元格交互
- 2. 分页导航控件:`PageNavigator`的状态管理
- 3. 表格页面封装:`QTablePages`的整合能力
- 4. 远程数据模型:`RemoteTableModel`的数据处理(核心)
- 三、实践经验与优化方向
- 四、组件协作关系图
- 五、总结
- 上篇文章中使用到了
QSqlTableModel
来管理与处理数据库的操作,但这个模型有一个很大的局限,就是它只能处理本地数据库,不能处理远程数据库,所以便开发了下述远程模式的model。
效果图
一、整体架构设计:基于MVC模式的分层实现
本次分享的表格系统严格遵循Qt的MVC(Model-View-Controller)设计模式,各组件职责清晰:
- 模型(Model):
RemoteTableModel
负责数据的获取、存储和处理,与后端数据源交互 - 视图(View):
ToolTipTableView
负责数据的可视化展示 - 委托(Delegate):
GeneralDelegate
处理单元格的自定义渲染和用户交互 - 控制器(Controller):
QTablePages
整合上述组件,协调数据流转与用户操作
此外,PageNavigator
作为独立的分页控件,负责页码管理;LogManagement
则承担数据模型的统一初始化与管理。这种分层设计让各组件可独立维护、灵活复用。
二、核心组件解析
1. 自定义委托:GeneralDelegate
实现单元格交互
表格中常需要在单元格中嵌入按钮等交互元素,GeneralDelegate
通过重写Qt委托的关键方法实现这一功能:
// 重写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
处理按钮点击事件,并通过信号将交互传递给上层:
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
作为上层封装类,将表格视图、数据模型和分页控件有机结合:
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阻塞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) {// 数据处理逻辑beginResetModel();m_records = newData;endResetModel();m_loading = false;emit loadFinished(newData);}); }
-
数据标准化:在
handleDataLoaded
中统一处理数据格式,确保视图展示一致性for (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通过异步回调 + 信号槽机制解决这一问题:
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功能
-
未来优化方向:
- 增加数据缓存机制减少重复请求
- 实现表格列的动态显示/隐藏
- 加入单元格编辑功能的统一处理
四、组件协作关系图
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ LogManagement │──┬──▶│ RemoteTableModel│◀────▶│DBClientThreadHandler│
└─────────────────┘ │ └────────┬────────┘ └─────────────────┘│ ││ ▼│ ┌─────────────────┐└──▶│ QTablePages │└────────┬────────┘│┌───────────────┼───────────────┐▼ ▼ ▼┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ToolTipTableView│ │PageNavigator│ │GeneralDelegate│└─────────────┘ └─────────────┘ └─────────────┘
五、总结
- 本文介绍的表格组件套装通过合理的架构设计和组件封装,实现了数据展示、分页导航、自定义交互等核心功能。基于Qt的MVC模式和信号槽机制,各组件既相互协作又保持独立,既保证了功能的完整性,又具备良好的可维护性和可扩展性。
- 代码中grpc通信模块没做展示,因为grpc通信模块比较简单,且不是本文的重点,所以没做展示。
- 核心代码