Qt自定义点击表头排序,使支持恢复默认顺序

0.前言

之前总结了 QTableView 实现排序 ,但是这里还有个问题,就是 Qt 默认的排序状态只有升序和降序,没法通过点击表头恢复到默认的顺序,他这个 SortOrder 宏也只有升序降序两个枚举值(有点奇怪,Qt 很多枚举都有 None 之类的):

    enum SortOrder {
        AscendingOrder,
        DescendingOrder
    };

没办法,要实现恢复默认顺序 ,只能自定义一个 QHeaderView ,最终效果如下:

(网上也有另一个类似的,不过实现细节有区别:https://www.cnblogs.com/swarmbees/p/11519796.html

1.实现思路

我们是通过点击表头来操作排序的,所以直接去 QHeaderView 源码找鼠标点击事件,看他怎么处理的。结果很遗憾,里面基本都是用的 private 类来操作,也没有什么相关的虚函数接口,只能重写下鼠标点击事件。

//源码片段,大部分都封装在了 private 类
void QHeaderView::mousePressEvent(QMouseEvent *e)
{
    Q_D(QHeaderView);
    if (d->state != QHeaderViewPrivate::NoState || e->button() != Qt::LeftButton)
        return;
    int pos = d->orientation == Qt::Horizontal ? e->x() : e->y();
    int handle = d->sectionHandleAt(pos);
    d->originalSize = -1; // clear the stored original size
    if (handle == -1) {
        d->pressed = logicalIndexAt(pos);
        if (d->clickableSections)
            emit sectionPressed(d->pressed);

        bool acceptMoveSection = d->movableSections;
        if (acceptMoveSection && d->pressed == 0 && !d->allowUserMoveOfSection0)
            acceptMoveSection = false; // Do not allow moving the tree nod

        if (acceptMoveSection) {
            d->section = d->target = d->pressed;
            if (d->section == -1)
                return;
            d->state = QHeaderViewPrivate::MoveSection;
            d->setupSectionIndicator(d->section, pos);
        } else if (d->clickableSections && d->pressed != -1) {
            updateSection(d->pressed);
            d->state = QHeaderViewPrivate::SelectSections;
        }
    } else if (sectionResizeMode(handle) == Interactive) {
        d->originalSize = sectionSize(handle);
        d->state = QHeaderViewPrivate::ResizeSection;
        d->section = handle;
        d->preventCursorChangeInSetOffset = false;
    }

    d->firstPos = pos;
    d->lastPos = pos;

    d->clearCascadingSections();
}

在鼠标按下时,我们保存按下的表头列号,此时也要判断下是否在拉伸列宽(我用的鼠标形状来判断,因为没有相关公有接口)。鼠标弹起时,判断列号相同则没有 move 交换位置(还可以和点击时的位置计算下移动距离),满足条件就将该列切换排序状态。

这里我自己定义了一个枚举:

    //排序状态
    enum SortOrder
    {
        NoOrder = 0 //未排序
        , DescOrder //降序
        , AscOrder  //升序
    };

切换排序状态的时候,先判断是否点击的上次那一列,是的话则用上一次的排序状态来计算下一次的,如果点击的另一列则直接用默认状态来计算下一次状态:

SortHeaderView::SortOrder SortHeaderView::getNextOrder(SortHeaderView::SortOrder order) const
{
    //状态循环:无排序-降序-升序
    switch (order)
    {
    case NoOrder: return DescOrder;
    case DescOrder: return AscOrder;
    case AscOrder: break;
    }
    return NoOrder;
}

切换状态后,我是通过信号槽通知外部来修改状态(一般可以放到 QTableView 进行):


    //排序自定义信号
    //如果是自定义view,这段加到view里去
    connect(header, &SortHeaderView::sortOrderChanged,
            this, [this,header,proxy_model](int logicIndex,SortHeaderView::SortOrder order){
        QTableView *view=ui->tableView;
        //无效index或NoOrder就设置为默认未排序状态
        if (logicIndex < 0 || order == SortHeaderView::NoOrder){
            //去掉排序三角样式
            header->setSortIndicator(-1, Qt::DescendingOrder);
            //-1则还原model默认顺序
            proxy_model->sort(-1, Qt::DescendingOrder);
        }
        else{
            switch (order) {
            case SortHeaderView::DescOrder:
                view->sortByColumn(logicIndex, Qt::DescendingOrder);
                break;
            case SortHeaderView::AscOrder:
                view->sortByColumn(logicIndex, Qt::AscendingOrder);
                break;
            }
        }
    });

Qt 文档显示,QSortFilterProxyModel sort 列号为 1 则恢复默认顺序,如果 Model sort 也是我们自己写的,就留一个对应的接口。 

另外,我们还可以设置不排序的列号,在点击时作判断。 

2.实现代码:

完整代码链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qt/SortHeaderView

主要实现:

#ifndef SORTHEADERVIEW_H
#define SORTHEADERVIEW_H

#include <QHeaderView>

/**
 * @brief 自定义表头,以支持取消排序状态
 */
class SortHeaderView : public QHeaderView
{
    Q_OBJECT
public:
    //排序状态
    enum SortOrder
    {
        NoOrder = 0 //未排序
        , DescOrder //降序
        , AscOrder  //升序
    };
public:
    explicit SortHeaderView(Qt::Orientation orientation = Qt::Horizontal,
                            QWidget *parent = nullptr);

    //不排序的行列
    //(为什么放header不放proxy?因为排序是点击header调用的)
    QList<int> getUnsortIndexs() const;
    void setUnsortIndexs(const QList<int> &rowOrColumns);
    void appendUnsortIndex(int rowOrColumn);

protected:
    //显示的时候同步indicator三角状态,
    //因为初始化可能是用的sortByColumn来排序,但是这个接口不是虚函数没法自定义
    //所以就在显示的时候同步下状态
    void showEvent(QShowEvent *event) override;
    // 点击表头
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    // 根据当前排序状态得到下一个状态
    SortOrder getNextOrder(SortOrder order) const;

signals:
    //因为view里关联了自带的sortIndicatorChanged,
    //所以自定义一个,避免冲突
    void sortOrderChanged(int index, SortOrder order);

private:
    // 不进行排序的行列
    QList<int> unsortIndexs;

    //鼠标按下时对应原model index
    int pressLogicIndex = -1;
    //鼠标按下时对应显示的index,可能是交换了位置的
    int pressVisualIndex = -1;
    //上一次点击对应的logic index
    int previousLogicIndex = -1;
    //当前排序状态
    SortOrder sortOrder = NoOrder;
};

#endif // SORTHEADERVIEW_H
#include "SortHeaderView.h"

#include <QMouseEvent>
#include <QCursor>

SortHeaderView::SortHeaderView(Qt::Orientation orientation, QWidget *parent)
    : QHeaderView(orientation,parent)
{
    //禁用默认的点击处理,这样就不会触发默认排序
    //自己处理点击来设置排序
    setSectionsClickable(false);
}

QList<int> SortHeaderView::getUnsortIndexs() const
{
    return unsortIndexs;
}

void SortHeaderView::setUnsortIndexs(const QList<int> &rowOrColumns)
{
    unsortIndexs = rowOrColumns;
}

void SortHeaderView::appendUnsortIndex(int rowOrColumn)
{
    unsortIndexs.push_back(rowOrColumn);
}

void SortHeaderView::showEvent(QShowEvent *event)
{
    QHeaderView::showEvent(event);
    //因为初始化可能是用的sortByColumn来排序,但是这个接口不是虚函数没法自定义
    //所以就在显示的时候同步下状态
    previousLogicIndex = sortIndicatorSection();
    if (previousLogicIndex >= 0) {
        sortOrder = (sortIndicatorOrder() == Qt::AscendingOrder) ? AscOrder : DescOrder;
    }
    else {
        sortOrder = NoOrder;
    }
}

void SortHeaderView::mousePressEvent(QMouseEvent *event)
{
    QHeaderView::mousePressEvent(event); //注意顺序

    if (event->button() != Qt::LeftButton)
        return;
    const int pos = (orientation() == Qt::Horizontal) ? event->x() : event->y();
    const int index = logicalIndexAt(pos);
    Qt::CursorShape shape = cursor().shape();

    //先判断index是否为有效的section,
    //判断cursor-shape是为了过滤点resize-handle情况
    pressLogicIndex = -1;
    pressVisualIndex = -1;
    if (index >= 0 && shape != Qt::SplitHCursor&&shape != Qt::SplitVCursor)
    {
        //排除不排序的index
        if (!unsortIndexs.contains(index))
        {
            pressLogicIndex = index;
            pressVisualIndex = visualIndex(index);
        }
    }
}

void SortHeaderView::mouseReleaseEvent(QMouseEvent *event)
{
    QHeaderView::mouseReleaseEvent(event); //注意顺序

    if (event->button() != Qt::LeftButton)
        return;
    const int pos = (orientation() == Qt::Horizontal) ? event->x() : event->y();
    const int index = logicalIndexAt(pos);
    const int visual_index = visualIndex(index);

    //弹起后再进行排序操作
    //此处还可以判断下鼠标的移动距离,暂略
    if (pressLogicIndex >= 0 && visual_index == pressVisualIndex)
    {
        //和上次点击的index比较,相同的就轮换排序状态,不同的就降序
        //放release不放pressed是为了避免双击时判断不准
        if (pressLogicIndex == previousLogicIndex)
        {
            sortOrder = getNextOrder(sortOrder);
        }
        else
        {
            sortOrder = getNextOrder(NoOrder);
        }
        emit sortOrderChanged(pressLogicIndex, sortOrder);
        previousLogicIndex = pressLogicIndex;
    }
    //注释不复位是为了避免双击时判断不准
    //pressLogicIndex=-1;
}

SortHeaderView::SortOrder SortHeaderView::getNextOrder(SortHeaderView::SortOrder order) const
{
    //状态循环:无排序-降序-升序
    switch (order)
    {
    case NoOrder: return DescOrder;
    case DescOrder: return AscOrder;
    case AscOrder: break;
    }
    return NoOrder;
}
//Ui中放了一个QTableView

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include "SortHeaderView.h"
#include <QRandomGenerator>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    initTable();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::initTable()
{
    //设置自定义的表头
    SortHeaderView *header=new SortHeaderView(Qt::Horizontal,ui->tableView);
    ui->tableView->setHorizontalHeader(header);
    //可以交换列
    header->setSectionsMovable(true);

    //单元格默认宽高
    ui->tableView->verticalHeader()->setDefaultSectionSize(24);
    ui->tableView->horizontalHeader()->setDefaultSectionSize(50);

    //数据model
    QStandardItemModel* model = new QStandardItemModel(this);
    const int col_max=10;
    const int row_max=20;
    //设置列表头
    model->setColumnCount(col_max);
    for(int i=0;i<col_max;i++)
        model->setHeaderData(i,Qt::Horizontal, QString("Col %1").arg(i));
    //设置行表头
    model->setRowCount(row_max);
    for(int i=0;i<row_max;i++)
        model->setHeaderData(i,Qt::Vertical, QString("Row %1").arg(i));

    //使用随机数填充
    QRandomGenerator *rand_gen=QRandomGenerator::global();
    int value=0;
    for(int i=0;i<row_max;i++){
        for(int j=0;j<col_max;j++){
            //第一列用行数,其他的用随机数
            value=(j==0)?(i): (rand_gen->bounded(0,100));
            QStandardItem *new_item=new QStandardItem(QString::number(value));
            //用文本排序的话,默认是字典顺序
            new_item->setData(value,Qt::InitialSortOrderRole);
            //文本居中
            new_item->setData(Qt::AlignCenter,Qt::TextAlignmentRole);
            model->setItem(i, j, new_item);
        }
    }

    //view会根据model提供的数据来渲染
    //ui->tableView->setModel(model);
    //屏蔽编辑
    ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);

    //设置排序
    //给普通的 ItemModel 加一层 SortProxy
    QSortFilterProxyModel *proxy_model = new QSortFilterProxyModel(this);
    //默认排序role应该是dispalyrole
    proxy_model->setSortRole(Qt::InitialSortOrderRole);
    proxy_model->setSourceModel(model);
    ui->tableView->setModel(proxy_model);
    //允许点击排序
    ui->tableView->setSortingEnabled(true);
    //按第0列升序
   // ui->tableView->sortByColumn(0,Qt::AscendingOrder);

    //排序自定义信号
    //如果是自定义view,这段加到view里去
    connect(header, &SortHeaderView::sortOrderChanged,
            this, [this,header,proxy_model](int logicIndex,SortHeaderView::SortOrder order){
        QTableView *view=ui->tableView;
        //无效index或NoOrder就设置为默认未排序状态
        if (logicIndex < 0 || order == SortHeaderView::NoOrder){
            //去掉排序三角样式
            header->setSortIndicator(-1, Qt::DescendingOrder);
            //QSortFilterProxyModel can be sorted by column -1,
            //in which case it returns to the sort order of the underlying source model.
            //-1则还原model默认顺序
            proxy_model->sort(-1, Qt::DescendingOrder);
        }
        else{
            switch (order) {
            case SortHeaderView::DescOrder:
                view->sortByColumn(logicIndex, Qt::DescendingOrder);
                break;
            case SortHeaderView::AscOrder:
                view->sortByColumn(logicIndex, Qt::AscendingOrder);
                break;
            }
        }
    });
}

 

  • 5
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值