前言
最近需求遇到了需要实现QHeaderView表头筛选的功能,
这个需求让我想起最开始用QT做数据处理的时候,
那时候菜,
同样的一个Qaiv案例改成QHeaderView标题筛选简直没法看,只能用,都不能说是拆轮子了,纯粹的乱来。
现在时隔几年又重新研究了一下,感觉这次还行,至少还能保留了轮子本身的框架,后面有需求还能再已有的基础上追加。
一番耗时研究,这里写个总结。
目录导读
关于Qaiv库
关于Qaiv库的作用描述
Qaiv (Qt Advanced Item Views) 是一个专门为 Qt框架 设计的、功能强大的开源扩展库。它的核心使命是极大地增强Qt原生的模型/视图(Model/View)组件(特别是QTableView)的数据处理与交互能力。(出自DeepSeek)
如图示:

其核心作用可以概括为以下三点:
1. 提供高级的筛选与排序功能
- 类Excel的表头筛选: 这是Qaiv最知名的特性。它允许开发者轻松地为QHeaderView的每一列添加功能完备的筛选器,支持按值列表多选、通配符匹配、正则表达式等多种筛选方式,远超原生QSortFilterProxyModel的功能。
- 强大的排序机制: 提供了比Qt原生更灵活的多列排序和自定义排序规则。
2. 实现便捷的数据分组与聚合
- Qaiv可以将表格行按某一列或多列的值进行可视化分组,形成可折叠/展开的树形结构。
在分组后,它还能自动为每个组计算聚合数据(如求和、平均值、计数等),并将结果显示在组标题行中,非常适合数据汇总和分析报表。
3. 简化复杂视图的开发
- Qaiv通过提供一系列高度集成且易于使用的代理模型(如CheckableProxyModel, FilterProxyModel, GroupByProxyModel等),将上述复杂功能封装成简单的API。开发者无需从零开始实现这些高级特性,只需将源模型与Qaiv的代理模型链式连接,即可快速构建出功能丰富的专业级数据表格应用,显著提升开发效率。
源码下载
按理说,类似与QHeaderView表头多项筛选的功能应该在数据处理方面应用很多,但实际上类似的开源软件我就只找到这一个,只能说这个库太全面了。
出自:https://sourceforge.net/projects/qadvanceditemviews/
具体下载地址:https://master.dl.sourceforge.net/project/qadvanceditemviews/0.4/qaiv-0.4.0.zip?viasf=1
在GitHub上也有大佬进行修改的版本,可以做个参考
https://github.com/Alexpux/QAdvancedItemViews
编译源码

Qaiv库使用Qt4开发的,其中QT库的引用都是通过链接器加载,同时添加QtWidgets库的引用,包括头文件。

也可以使用QT Vs开发工具,
- 修改项目结构,
- 再添加QtWidgets模块,
- 移除属性-》链接器中的lib引用,
- 添加mscv编译器
就能直接编译生成了。

如下图示修改:

同理,其他的模块也都一样修改。
其中example模块包含了Qaiv库中所有模块的示例,
QHeaderView表头多选筛选只需要 QAbstractFilterProxyModel 模型相关功能,
所以只对此Model进行了修改。
重新QHeaderView标题实现筛选
首先展示下效果:

整个示例相当于将Qaiv库功能阉割了,
只保留了一项多项筛选功能。
重写QHeaderView类
- 完整源码:
QFilterHeaderView.h
#pragma execution_character_set("utf-8")
#include <QHeaderView>
#include <QObject>
#include <QWidget>
#include <QPainter>
#include <QToolBox>
#include <QToolButton>
#include <QAbstractItemModel>
#include <QDebug>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QLabel>
#include <QtCore/QtGlobal>
#include <QPushButton>
#include <QScopedPointer>
#include <QSortFilterProxyModel>
//! 私有类 实现相关功能
class QFilterHeaderViewPrivate;
//! 自定义表格视图
class QFilterHeaderView : public QHeaderView
{
Q_OBJECT
public:
QFilterHeaderView(Qt::Orientation orientation, QWidget* parent = nullptr);
~QFilterHeaderView();
//! 重写绘图
void paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const override;
//! 刷新列
void updateSection(int logicalIndex);
protected:
bool event(QEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
private:
QScopedPointer<QFilterHeaderViewPrivate> d_ptr;
Q_DECLARE_PRIVATE(QFilterHeaderView)
};
QFilterHeaderView.cpp:
#include "QFilterHeaderView.h"
#include "qabstractfilterproxymodel.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QFontMetrics>
#include "FontIconTool.h"
class QFilterHeaderViewPrivate
{
QFilterHeaderView* q_ptr;
Q_DECLARE_PUBLIC(QFilterHeaderView)
public:
QFilterHeaderViewPrivate();
//! 获取每列图标对应位置
QRect FilterRect(int logicalIndex);
//! 点击时选中的列
int selectLogicalIndex = -1;
bool IsSelectItem = false;
};
///////////////////////////// CustomHeaderViewPrivate /////////////////////////////
QFilterHeaderViewPrivate::QFilterHeaderViewPrivate()
{
selectLogicalIndex = -1;
IsSelectItem = false;
}
QRect QFilterHeaderViewPrivate::FilterRect(int logicalIndex)
{
int pos = q_ptr->sectionViewportPosition(logicalIndex); // 起始位置(视口坐标)
int size = q_ptr->sectionSize(logicalIndex); // 宽度(水平表头)或高度(垂直表头)
int Position = q_ptr->sectionPosition(logicalIndex);
QRect rect;
if (q_ptr->orientation() == Qt::Horizontal) {
rect = QRect(pos, 0, size, q_ptr->height());
}
else {
rect = QRect(0, pos, q_ptr->width(), size);
}
//! 固定筛选字符串
QString text = QChar(FontIconTool::FontIconId::fa_filter);
QFont font = q_ptr->font();
QFontMetrics fm(font);
//! 字体符号对应外边距
QRect textRect = fm.boundingRect(text);
int x = rect.x() + rect.width() - textRect.width() - 10;
int y = (rect.y() + rect.height() - textRect.height()) / 2;
//! 扩大 尽量标准
textRect = QRect(x, y, textRect.width(), textRect.height());
QRect backrect = textRect.adjusted(-3, -2, 3, 2);
return backrect;
}
///////////////////////////// CustomHeaderView /////////////////////////////
QFilterHeaderView::QFilterHeaderView(Qt::Orientation orientation, QWidget* parent)
:QHeaderView(orientation, parent), d_ptr(new QFilterHeaderViewPrivate)
{
d_ptr->q_ptr = this;
QFont font = FONTICONTOOL->getFont();
this->setFont(font);
show();
}
QFilterHeaderView::~QFilterHeaderView()
{
}
void QFilterHeaderView::paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const
{
QHeaderView::paintSection(painter, rect, logicalIndex);
painter->restore();
// 获取 section 的位置和大小
int pos = sectionViewportPosition(logicalIndex); // 起始位置(视口坐标)
int size = sectionSize(logicalIndex); // 宽度(水平表头)或高度(垂直表头)
int Position = sectionPosition(logicalIndex);
QAbstractFilterProxyModel* filtermodel = static_cast<QAbstractFilterProxyModel*>(model());
if (filtermodel!= nullptr)
{
if (filtermodel->IsHaveFilter(logicalIndex))
{
//! 范围内
QString text = QChar(FontIconTool::FontIconId::fa_filter);
QFontMetrics fm(this->font());
QRect textRect = fm.boundingRect(text);
int x = rect.x() + rect.width() - textRect.width() - 10;
int y = (rect.y() + rect.height() - textRect.height()) / 2;
textRect = QRect(x, y, textRect.width(), textRect.height());
QBrush backGround = painter->background();
if (filtermodel->IsActivateFilter(logicalIndex))
backGround = QBrush(QColor("#009600"));
painter->fillRect(textRect, backGround);
QRect backrect = textRect.adjusted(-3, -2, 3, 2);
painter->fillRect(backrect, backGround);
painter->drawRect(backrect);
painter->drawText(textRect.bottomLeft(), text);
}
}
painter->save();
}
bool QFilterHeaderView::event(QEvent* event)
{
return QHeaderView::event(event);
}
void QFilterHeaderView::mousePressEvent(QMouseEvent* event)
{
QHeaderView::mousePressEvent(event);
QAbstractFilterProxyModel* filtermodel = static_cast<QAbstractFilterProxyModel*>(model());
if (filtermodel == nullptr)
return;
int logicalIndex = logicalIndexAt(event->pos());
if (logicalIndex >= 0 && filtermodel->IsHaveFilter(logicalIndex)) {
d_ptr->selectLogicalIndex = logicalIndex;
if (d_ptr->FilterRect(logicalIndex).contains(event->pos()))
{
d_ptr->IsSelectItem = true;
}
}
}
void QFilterHeaderView::mouseReleaseEvent(QMouseEvent* event)
{
QHeaderView::mouseReleaseEvent(event);
QAbstractFilterProxyModel* filtermodel = static_cast<QAbstractFilterProxyModel*>(model());
if (filtermodel == nullptr)
return;
// 获取点击位置的逻辑索引(第几个section)
int logicalIndex = logicalIndexAt(event->pos());
if (logicalIndex >= 0 && filtermodel->IsHaveFilter(logicalIndex)) {
if (d_ptr->IsSelectItem && d_ptr->selectLogicalIndex == logicalIndex &&
d_ptr->FilterRect(logicalIndex).contains(event->pos()))
{
int pos = sectionViewportPosition(logicalIndex); // 起始位置(视口坐标)
//int size = sectionSize(logicalIndex); // 宽度(水平表头)或高度(垂直表头)
filtermodel->showPopup(logicalIndex, this->mapToGlobal(QPoint(pos-1, this->height()-1)));
}
}
d_ptr->IsSelectItem = false;
}
void QFilterHeaderView::updateSection(int logicalIndex)
{
QHeaderView::updateSection(logicalIndex);
}
重写QAbstractFilterProxyModel类
QAbstractFilterProxyModel.h
#include <QObject>
#include <QSize>
#include <QSortFilterProxyModel>
//! 重写筛选类
class QAbstractFilterProxyModelPrivate;
class QAbstractFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
QAbstractFilterProxyModel(QObject* parent = 0);
~QAbstractFilterProxyModel();
//! 创建一个筛选器
QWidget* createfilter(QWidget* parent, const QModelIndex index, int type);
//! 设置数据源
void setSourceModel(QAbstractItemModel* sourceModel);
//! 是否有筛选器
bool IsHaveFilter(int column);
//! 是否应用筛选
bool IsActivateFilter(int column);
//! 弹出筛选窗
void showPopup(int column, QPoint pos);
//! 清理数据
void ClearData();
signals:
/**
* 此信号会在滤波电流过滤功能失效之前发出。
* @see resultChanged()
*/
void resultAboutToChange();
/**
* 此信号是在过滤过程完成后发出的。
* @see resultChanged(), resultCountChanged()
*/
void resultChanged();
/**
* 每当过滤后的结果集中的行数发生变化时,就会发出此信号。
*/
void resultCountChanged(int filteredRows, int unfilteredRows);
public slots:
void updateResult();
protected:
void emitResultCountChanged();
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
private:
QScopedPointer<QAbstractFilterProxyModelPrivate> d_ptr;
Q_DECLARE_PRIVATE(QAbstractFilterProxyModel)
};
#include "qabstractfilter.h"
#include <QDebug>
#include "qautofilter.h"
class QAbstractFilterProxyModelPrivate
{
QAbstractFilterProxyModel* q_ptr;
Q_DECLARE_PUBLIC(QAbstractFilterProxyModel)
public:
QAbstractFilterProxyModelPrivate();
~QAbstractFilterProxyModelPrivate();
//! 筛选组合
QList<QAbstractFilter*> FilterGroup;
//! 最后一次数据改变的个数
int lastResultCount;
};
///////////////////////////// QAbstractFilterProxyModelPrivate /////////////////////////////
QAbstractFilterProxyModelPrivate::QAbstractFilterProxyModelPrivate()
{
}
QAbstractFilterProxyModelPrivate::~QAbstractFilterProxyModelPrivate()
{
}
///////////////////////////// QAbstractFilterProxyModel /////////////////////////////
QAbstractFilterProxyModel::QAbstractFilterProxyModel(QObject* parent) :
QSortFilterProxyModel(parent), d_ptr(new QAbstractFilterProxyModelPrivate())
{
d_ptr->q_ptr = this;
}
QAbstractFilterProxyModel::~QAbstractFilterProxyModel()
{
}
void QAbstractFilterProxyModel::setSourceModel(QAbstractItemModel* sourceModel)
{
QSortFilterProxyModel::setSourceModel(sourceModel);
for (int i = 0; i < d_ptr->FilterGroup.count(); i++)
d_ptr->FilterGroup[i]->setModelData(sourceModel );
}
QWidget* QAbstractFilterProxyModel::createfilter(QWidget* parent, const QModelIndex index, int type)
{
QVariantMap properties;
properties["column"] = index.column();
properties["row"] = index.row();
properties["type"] = QAutoFilter::Type;
QAbstractFilter* filter = new QAutoFilter(index.row(), index.column());
d_ptr->FilterGroup.append(filter);
filter->setEnabled(true);
return filter->createEditor(parent, this);
}
bool QAbstractFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
{
// qDebug()<<"filterAcceptsRow--> "<<source_row;
bool rr = true;
int fc = 0;
int t = -1; //! 简化了类结构,此处无用
Q_FOREACH(QAbstractFilter * f, d_ptr->FilterGroup) {
//! 有一个不符合条件就退出
if (!f->matches(sourceModel()->data(sourceModel()->index(source_row, f->column())), t)) {
return false;
}
}
return rr;
}
void QAbstractFilterProxyModel::updateResult()
{
// reset last result count
d_ptr->lastResultCount = -1;
emit resultAboutToChange();
// invalidate filter
qDebug() << "invalidateFilter -->start;";
invalidateFilter();
qDebug() << "invalidateFilter -->end;";
//
emit resultChanged();
// emit result count changed
emitResultCountChanged();
}
void QAbstractFilterProxyModel::emitResultCountChanged()
{
if (rowCount() != d_ptr->lastResultCount) {
d_ptr->lastResultCount = rowCount();
emit resultCountChanged(d_ptr->lastResultCount, sourceModel()->rowCount());
}
}
bool QAbstractFilterProxyModel::IsHaveFilter(int column)
{
Q_FOREACH(QAbstractFilter * f, d_ptr->FilterGroup) {
if (f->column() == column)
return true;
}
return false;
}
void QAbstractFilterProxyModel::showPopup(int column, QPoint pos)
{
Q_FOREACH(QAbstractFilter * f, d_ptr->FilterGroup) {
if (f->column() == column)
f->showPopup(pos);
}
}
void QAbstractFilterProxyModel::ClearData()
{
d_ptr->FilterGroup.clear();
invalidate();
delete sourceModel();
}
bool QAbstractFilterProxyModel::IsActivateFilter(int column)
{
Q_FOREACH(QAbstractFilter * f, d_ptr->FilterGroup) {
if (f->column() == column)
return f->IsActivateFilter();
}
return false;
}
重写QAbstractFilter筛选单项基类
QAbstractFilter.h:
#include <QMap>
#include <QMenu>
#include <QStyleOptionViewItem>
#include <QSortFilterProxyModel>
#include <QVariant>
//! 私有方法实现
class QAbstractFilterPrivate;
//! 筛选基类
class QAbstractFilter
{
public:
//! 类型
static const int UserType = 65536;
enum {
Type = -1
};
QAbstractFilter();
QAbstractFilter(const QMap<QString, QVariant>& properties);
~QAbstractFilter();
//!-----常用属性-----
int column() const;
int row() const;
QColor highlightColor() const;
bool isEnabled() const;
bool isValid() const;
QVariant property(const QString& key, const QVariant& defaultValue = QVariant()) const;
QMap<QString, QVariant> properties() const;
void setHighlightColor(const QColor& color);
void setEnabled(bool on);
void setProperty(const QString& name, const QVariant& value);
int type() const;
//!-----相关接口-----
//! 创建筛选窗体
virtual QWidget* createEditor(QWidget* parent, QSortFilterProxyModel* model) = 0;
//! 弹出窗体
virtual void showPopup(QPoint pos) = 0;
//! 返回默认值
virtual QVariant data(int role = Qt::DisplayRole) const = 0;
//! 匹配内容
virtual bool matches(const QVariant& value, int type = -1) const = 0;
//!设置默认选项
virtual void setEditorData(QVariantList data) = 0;
///设置源模式数据源
virtual void setModelData(QAbstractItemModel* model) = 0;
/// 是否有激活筛选项
virtual bool IsActivateFilter() = 0;
protected:
QAbstractFilter(int type, int row, int column);
private:
friend class QAbstractFilterModel;
void setRow(int row);
QAbstractFilterPrivate* d;
};
重写QAutoFilter类
QAutoFilter.h
#include <QObject>
#include <QScopedPointer>
#include "QAbstractFilter.h"
class QAutoFilterPrivate;
class QAutoFilter : public QAbstractFilter
{
public:
enum {
Type = 0
};
QAutoFilter(QObject *parent);
~QAutoFilter();
QAutoFilter(int row, int column);
//!-----实现相关接口-----
QWidget* createEditor(QWidget* parent, QSortFilterProxyModel* model);
void showPopup(QPoint pos);
QVariant data(int role = Qt::DisplayRole) const;
bool matches(const QVariant& value, int type) const;
void setEditorData(QVariantList);
void setModelData(QAbstractItemModel* model);
bool IsActivateFilter();
private:
QScopedPointer<QAutoFilterPrivate> d_ptr;
Q_DECLARE_PRIVATE(QAutoFilter)
};
QAutoFilter.cpp
#include <QDebug>
#include "qautofilterwidget.h"
#include "qabstractfilterproxymodel.h"
class QAutoFilterPrivate
{
QAutoFilter* q_ptr;
Q_DECLARE_PUBLIC(QAutoFilter)
public:
QAutoFilterPrivate();
~QAutoFilterPrivate();
//! 筛选弹窗
QAutoFilterWidget* filterWidget = nullptr;
};
///////////////////////////// QAutoFilterPrivate /////////////////////////////
QAutoFilterPrivate::QAutoFilterPrivate()
{
}
QAutoFilterPrivate::~QAutoFilterPrivate()
{
}
///////////////////////////// QAutoFilter /////////////////////////////
QAutoFilter::QAutoFilter(int row, int column) :
QAbstractFilter(QAutoFilter::Type, row, column), d_ptr(new QAutoFilterPrivate)
{
}
QAutoFilter::~QAutoFilter()
{
}
QWidget* QAutoFilter::createEditor(QWidget* parent, QSortFilterProxyModel* model)
{
d_ptr->filterWidget = new QAutoFilterWidget(parent);
QAbstractFilterProxyModel* p = qobject_cast<QAbstractFilterProxyModel*>(model);
setModelData(p->sourceModel());
QObject::connect(d_ptr->filterWidget, &QAutoFilterWidget::UpdataEditorData, [&](QVariantList data) {
setEditorData(data);
});
QObject::connect(d_ptr->filterWidget, &QAutoFilterWidget::modeChanged, p, &QAbstractFilterProxyModel::updateResult);
return d_ptr->filterWidget;
}
void QAutoFilter::showPopup(QPoint pos)
{
d_ptr->filterWidget->move(pos);
d_ptr->filterWidget->show();
}
QVariant QAutoFilter::data(int role) const
{
if (role == Qt::DisplayRole) {
if (property("mode").toInt() == 0) {
if (property("selectedValues").toList().isEmpty()) {
return QObject::tr("<none>");
}
else {
if (property("selectedValues").toList().size() == 1) {
return QString(QObject::tr("%1 entry")).arg(property("selectedValues").toList().size());
}
else {
return QString(QObject::tr("%1 entries")).arg(property("selectedValues").toList().size());
}
}
}
else if (property("mode").toInt() == 1) {
return QObject::tr("Empty");
}
else if (property("mode").toInt() == 2) {
return QObject::tr("Not Empty");
}
}
return QVariant();
}
bool QAutoFilter::matches(const QVariant& value, int type) const
{
Q_UNUSED(type);
if (property("mode").toInt() == 1) {
return value.toString().isEmpty();
}
else if (property("mode").toInt() == 2) {
return !value.toString().isEmpty();
}
//! 没有勾选默认全部
if (property("selectedValues").toList().count() == 0)
return true;
return property("selectedValues").toList().contains(value);
}
void QAutoFilter::setEditorData(QVariantList data)
{
setProperty("selectedValues", data);
}
void QAutoFilter::setModelData(QAbstractItemModel* model)
{
setProperty("selectedValues", QVariantList());
d_ptr->filterWidget->setSourceModel(model, column());
d_ptr->filterWidget->setSelectedValues(property("selectedValues").toList());
}
bool QAutoFilter::IsActivateFilter()
{
return property("selectedValues").toList().count() > 0;
}
完整调用:
本身Qaiv库的筛选是多个控件的组合实现,重写了单元的ItemDelegate 委托,实现筛选项的内容编辑,重写了bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) 方法实现了多项筛选条件的判断。
这里我进行了删减,移除了不需要的部分,只保留了基本的调用
//! 数据源
//! QStandardItemModel* m_model;
QAbstractFilterProxyModel* filtermodel = new QAbstractFilterProxyModel();
filtermodel->setSourceModel(m_model);
ui->tableView->setModel(filtermodel);
//添加筛选表头
QFilterHeaderView* view = new QFilterHeaderView(Qt::Horizontal, ui->tableView);
view->setModel(filtermodel);
//!绑定表标题,隐藏行标题
ui->tableView->setHorizontalHeader(view);
ui->tableView->verticalHeader()->hide();
//! 添加筛选项
//ui.tableView_Statistics->horizontalHeader()->show();
for (int i = 0; i <m_model->columnCount(); i++)
{
filtermodel->createfilter(view, m_model->index(0, i), (int)QAutoFilter::Type);
}
connect(filtermodel, &QAbstractFilterProxyModel::resultChanged, this, [&, filtermodel]() {
qDebug()<<"Filter Update: "<<filtermodel->rowCount();
});
最近感觉需要点柚子叶去去霉运,毫无精神。
具体的说明懒得写了,请看源码,
功能本身不复杂,只是需要花点时间。。。
904

被折叠的 条评论
为什么被折叠?



