model/view 筛选功能

我的QTableView有点漏洞,有的列的信息很多,我希望它可以换行,不然一列就太宽了-CSDN博客

在此版本的基础上,我想要可以筛选特定的命令码,特定的发送方地址,接收方地址。


AI确定牛


messagefiltermodel.h / messagefiltermodel.cpp --- 继承 QSortFilterProxyModel,重写 filterAcceptsRow 实现多列同时筛选

cpp 复制代码
#ifndef MESSAGEFILTERMODEL_H
#define MESSAGEFILTERMODEL_H

#include <QSortFilterProxyModel>
#include <QSet>

class MessageFilterModel : public QSortFilterProxyModel
{
    Q_OBJECT

public:
    explicit MessageFilterModel(QObject *parent = nullptr);

public slots:
    /// 设置命令码筛选(空字符串表示不筛选)
    void setCommandCodeFilter(const QString &code);

    /// 设置发送方地址筛选(空字符串表示不筛选)
    void setSenderFilter(const QString &sender);

    /// 设置接收方地址筛选(空字符串表示不筛选)
    void setReceiverFilter(const QString &receiver);

    /// 设置是否启用模糊匹配(默认 true,即包含即匹配;false 为精确匹配)
    void setFuzzyMatch(bool enabled);

protected:
    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;

private:
    bool matchValue(const QString &value, const QString &filter) const;

    QString m_commandCodeFilter;
    QString m_senderFilter;
    QString m_receiverFilter;
    bool m_fuzzyMatch;
};

#endif // MESSAGEFILTERMODEL_H
cpp 复制代码
#include "messagefiltermodel.h"
#include "messagetablemodel.h"

MessageFilterModel::MessageFilterModel(QObject *parent)
    : QSortFilterProxyModel(parent)
    , m_fuzzyMatch(true)
{
    setDynamicSortFilter(true);
}

void MessageFilterModel::setCommandCodeFilter(const QString &code)
{
    m_commandCodeFilter = code.trimmed();
    invalidateFilter();
}

void MessageFilterModel::setSenderFilter(const QString &sender)
{
    m_senderFilter = sender.trimmed();
    invalidateFilter();
}

void MessageFilterModel::setReceiverFilter(const QString &receiver)
{
    m_receiverFilter = receiver.trimmed();
    invalidateFilter();
}

void MessageFilterModel::setFuzzyMatch(bool enabled)
{
    m_fuzzyMatch = enabled;
    invalidateFilter();
}

bool MessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
    QModelIndex idx;

    // 命令码筛选
    if (!m_commandCodeFilter.isEmpty()) {
        idx = sourceModel()->index(sourceRow, MessageTableModel::CommandCode, sourceParent);
        if (!matchValue(idx.data().toString(), m_commandCodeFilter))
            return false;
    }

    // 发送方地址筛选
    if (!m_senderFilter.isEmpty()) {
        idx = sourceModel()->index(sourceRow, MessageTableModel::SenderAddress, sourceParent);
        if (!matchValue(idx.data().toString(), m_senderFilter))
            return false;
    }

    // 接收方地址筛选
    if (!m_receiverFilter.isEmpty()) {
        idx = sourceModel()->index(sourceRow, MessageTableModel::ReceiverAddress, sourceParent);
        if (!matchValue(idx.data().toString(), m_receiverFilter))
            return false;
    }

    return true;
}

bool MessageFilterModel::matchValue(const QString &value, const QString &filter) const
{
    if (filter.isEmpty())
        return true;

    if (m_fuzzyMatch) {
        return value.contains(filter, Qt::CaseInsensitive);
    } else {
        return value.compare(filter, Qt::CaseInsensitive) == 0;
    }
}
cpp 复制代码
#include <QApplication>
#include <QTableView>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QLabel>
#include <QHeaderView>
#include <QWidget>

#include "messagetablemodel.h"
#include "messagefiltermodel.h"
#include "wordwrapdelegate.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    auto *model = new MessageTableModel;

    int cmd = 0;
    int send = 200;
    int rec = 600;
    for (int i = 0; i < 20 * 1000; i++)
    {
        cmd++;
        send++;
        rec++;
        model->addMessage({QString::number(cmd), QString::number(send), QString::number(rec),
                           "这是一段很长的报文内容,用来测试自动换行显示效果。"
                           "当报文内容超出单元格宽度时,文字会自动换到下一行显示,"
                           "确保用户可以看到完整的内容。"
                           "这是一段很长的报文内容,用来测试自动换行显示效果。"
                           "当报文内容超出单元格宽度时,文字会自动换到下一行显示,"
                           "确保用户可以看到完整的内容。"
                           "这是一段很长的报文内容,用来测试自动换行显示效果。"
                           "当报文内容超出单元格宽度时,文字会自动换到下一行显示,"
                           "确保用户可以看到完整的内容。"
                           "这是一段很长的报文内容,用来测试自动换行显示效果。"
                           "当报文内容超出单元格宽度时,文字会自动换到下一行显示,"
                           "确保用户可以看到完整的内容。"
                           "这是一段很长的报文内容,用来测试自动换行显示效果。"
                           "当报文内容超出单元格宽度时,文字会自动换到下一行显示,"
                           "确保用户可以看到完整的内容。"});
    }

    // 筛选代理模型
    auto *filterModel = new MessageFilterModel;
    filterModel->setSourceModel(model);

    auto *tableView = new QTableView;
    tableView->setModel(filterModel);
    tableView->horizontalHeader()->setStretchLastSection(true);
    tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
    tableView->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
    tableView->setWordWrap(true);

    // 给"报文内容"列设置换行 delegate
    auto *contentDelegate = new WordWrapDelegate(tableView);
    contentDelegate->setFixedRowHeight(60);
    tableView->setItemDelegateForColumn(MessageTableModel::Content, contentDelegate);

    // 设置统一行高
    tableView->verticalHeader()->setDefaultSectionSize(60);

    // --- 筛选输入框 ---
    auto *cmdEdit = new QLineEdit;
    cmdEdit->setPlaceholderText(QStringLiteral("输入命令码筛选..."));

    auto *senderEdit = new QLineEdit;
    senderEdit->setPlaceholderText(QStringLiteral("输入发送方地址筛选..."));

    auto *receiverEdit = new QLineEdit;
    receiverEdit->setPlaceholderText(QStringLiteral("输入接收方地址筛选..."));

    auto *filterLayout = new QHBoxLayout;
    filterLayout->addWidget(new QLabel(QStringLiteral("命令码:")));
    filterLayout->addWidget(cmdEdit, 1);
    filterLayout->addSpacing(10);
    filterLayout->addWidget(new QLabel(QStringLiteral("发送方:")));
    filterLayout->addWidget(senderEdit, 1);
    filterLayout->addSpacing(10);
    filterLayout->addWidget(new QLabel(QStringLiteral("接收方:")));
    filterLayout->addWidget(receiverEdit, 1);

    // 连接筛选信号
    QObject::connect(cmdEdit, &QLineEdit::textChanged,
                     filterModel, &MessageFilterModel::setCommandCodeFilter);
    QObject::connect(senderEdit, &QLineEdit::textChanged,
                     filterModel, &MessageFilterModel::setSenderFilter);
    QObject::connect(receiverEdit, &QLineEdit::textChanged,
                     filterModel, &MessageFilterModel::setReceiverFilter);

    auto *mainLayout = new QVBoxLayout;
    mainLayout->addLayout(filterLayout);
    mainLayout->addWidget(tableView);

    QWidget window;
    window.setWindowTitle(QStringLiteral("报文模型视图示例"));
    window.resize(900, 500);
    window.setLayout(mainLayout);
    window.show();

    return app.exec();
}

架构说明:

cpp 复制代码
数据源模型 (MessageTableModel)
        ↓ setSourceModel
筛选代理模型 (MessageFilterModel)
        ↓ setModel
QTableView

使用 QSortFilterProxyModel作为中间层,原数据模型完全不受影响,筛选只是视图层面的过滤。

筛选特性:

  • 三个输入框同时生效,是「与」的关系(命令码匹配 AND 发送方匹配 AND 接收方匹配)
  • 默认模糊匹配(包含即命中,不区分大小写)
  • 输入框清空即取消该列筛选
  • 实时响应(边输入边筛选)

如果需要精确匹配(必须完全相等),调用:

cpp 复制代码
filterModel->setFuzzyMatch(false);

此时数据量为20000。

当数据量为10万时,筛选功能出现了卡顿情况:

又让ai优化了一版:

优化点总结:

优化 说明
防抖定时器 300ms 输入时不会立即触发筛选,等用户停止输入 300ms 后才执行一次,避免逐字符全量扫描
关闭 setDynamicSortFilter 不再每次数据变化都自动刷新,由定时器手动控制 invalidateFilter() 的时机
直接访问数据 filterAcceptsRow 内通过 messageAt(row) 直接拿到 const MessageItem&,绕过了 **sourceModel()->index() + data()**的 QModelIndex 创建和 QVariant 转换开销

10 万行数据下,原来每输入一个字符就全量扫描 10 万行 × 3 次 index() 调用;现在输入过程中不触发任何筛选,停止输入 300ms 后才执行一次,且每次扫描直接用 const MessageItem& 引用访问字段,没有中间对象创建开销。

如果需要调整延迟时间:

cpp 复制代码
filterModel->setDebounceDelay(500); // 改为 500ms

确实好了一些,这个速度还可以,挺好的。

相关推荐
努力实干1 小时前
Qt/C++ 模块化设计与工程组织
开发语言·c++·qt
Lhan.zzZ2 小时前
Qt:动态库模块框架设计
开发语言·c++·qt
qq_401700411 天前
TCP 长连接、心跳包与断线自动重连设计:工业级通信的“不死鸟”架构
网络·qt·tcp/ip
dyxal2 天前
A2A 协议 v1.0 完全指南:方法名、Agent Card、三种绑定与 FastAPI 实战
开发语言·qt·fastapi
金色熊族2 天前
在Qt中使用asan检测内存泄漏(一)
linux·qt·asan
前行中的小猪2 天前
Qt 5 踩坑实录:多显示器跨屏拖拽导致窗口异常放大与 setGeometry 报错的深度解析与根治
qt·多显示器·高分辨率·多屏幕
想做小南娘,发现自己是女生喵2 天前
QT项目实战第一周(嵌入式)
开发语言·qt
誰能久伴不乏2 天前
【无标题】
c++·qt·机器人
金色熊族2 天前
在Qt中使用asan检测内存泄漏(二)--检查依赖库的内存使用
linux·qt·asan
qq_401700412 天前
Qt 组播(Multicast)实战详解
开发语言·qt