Qt表格组件封装与远程数据库连接:从数据展示到交互体验

文章目录

  • 上篇文章中使用到了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阻塞

    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) {
                // 数据处理逻辑
                beginResetModel();
                m_records = newData;
                endResetModel();
                m_loading = false;
                emit loadFinished(newData);
            });
    }
  • 数据标准化 :在handleDataLoaded中统一处理数据格式,确保视图展示一致性

    cpp 复制代码
    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支持 :提供insertRowupdateRowremoveRow等方法,封装数据操作逻辑

  • 远程数据操作最核心的挑战是避免 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()成对调用,告知视图重新加载数据,避免界面错乱

三、实践经验与优化方向

  1. 性能优化

    • 采用异步加载避免UI卡顿
    • 数据分页减少一次性加载压力
    • 模型重置时使用beginResetModelendResetModel确保视图高效更新
  2. 可扩展性设计

    • LogManagement中通过统一方法初始化不同业务模型,便于新增表格类型
    • 委托类与业务逻辑分离,可根据需求定制不同单元格样式
  3. 用户体验提升

    • 分页按钮状态动态反馈
    • 单元格tooltip提示完整信息
    • 支持数据导出CSV功能
  4. 未来优化方向

    • 增加数据缓存机制减少重复请求
    • 实现表格列的动态显示/隐藏
    • 加入单元格编辑功能的统一处理

四、组件协作关系图

plaintext 复制代码
┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
│   LogManagement │──┬──▶│ RemoteTableModel│◀────▶│DBClientThreadHandler│
└─────────────────┘  │   └────────┬────────┘      └─────────────────┘
                     │            │
                     │            ▼
                     │   ┌─────────────────┐
                     └──▶│   QTablePages   │
                         └────────┬────────┘
                                  │
                  ┌───────────────┼───────────────┐
                  ▼               ▼               ▼
         ┌─────────────┐  ┌─────────────┐  ┌─────────────┐
         │ToolTipTableView│  │PageNavigator│  │GeneralDelegate│
         └─────────────┘  └─────────────┘  └─────────────┘

五、总结

  • 本文介绍的表格组件套装通过合理的架构设计和组件封装,实现了数据展示、分页导航、自定义交互等核心功能。基于Qt的MVC模式和信号槽机制,各组件既相互协作又保持独立,既保证了功能的完整性,又具备良好的可维护性和可扩展性。
  • 代码中grpc通信模块没做展示,因为grpc通信模块比较简单,且不是本文的重点,所以没做展示。
  • 核心代码