QRowTable表格控件(二)-红涨绿跌

原文连接:QRowTable表格控件(二)-红涨绿跌浏览器

1、开心一刻

一天,五娃和六娃去跟蛇精决斗,决斗前有这样一段对话。app

五娃:“妖精!今天我俩就要消灭你!今天就是你的死期!”less

蛇精:“呵呵呵,真是好笑。大家本身个儿都是从树上长出来的,凭什么叫我妖精?!”ide

五娃:“你也说了,咱们是从树上长出来的,是葫芦变的,天然不是妖精。”函数

蛇精:“大家不是妖精,难道仍是神仙了,再难不成你把本身当人了?”字体

五娃和六娃异口同声道:“哈哈哈哈哈哈!你说对了,我就是人,是植!物!人!”优化

2、概述

最近工做比较忙,不过仍是抽时间把这个表格组件继续完善下去。ui

Qt的自带的表格控件很是强大,支持各类方便操做,上一篇文章QRowTable表格控件-支持hover整行、checked整行、指定列排序等介绍了一个简单的demo,主要是作一个股票表格控件,而他自然的就是支持hover和checked行特性,对Qt比较熟悉的同窗可能都知道Qt其实有两个接口,能够设置交互行为为选择行。this

接口就是这两个货了。

setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QTableView::SingleSelection);//不能多选

既然Qt已经有接口了,为何咱们还要本身写这个控件呢!

尝试过用Qt的接口设置相关行为的同窗我相信最后都会发现是什么缘由,这里我直接把缘由放出来。

  1. 首先咱们的需求是每一行的文字颜色是不同的,Qt表格默认的行为是:表格cell若是被选中,前景色和背景色则是从高亮role中拿到的色值,如下代码是一个示例代码,其中的QPalette::HighlightedText和QPalette::Highlight就是存储单元格被选中时,绘制的颜色。
view_option.palette.setColor(QPalette::HighlightedText, index.data(Qt::ForegroundRole).value<QColor>());
    view_option.palette.setColor(QPalette::Highlight, index.data(Qt::BackgroundRole).value<QColor>());
  1. 最重要的时候咱们本身仍是实现了一堆的小需求,下一小节咱们来一个个分析

3、效果展现

如下是红涨绿跌效果图,实现功能同样。

UI展示形式不同,可是都实现了红涨绿跌

  1. 背景色不一样
  2. 排序效果不一样

纯白版






腹黑版






4、任务需求

看过效果图以后,有没有发现咱们这里的表格控件和Qt自带的控件有什么区别呢?其实这里咱们要达到gif展现的那种效果,仍是使用了不少实现技巧。

控件都包含哪些功能呢?

  1. 指定列排序

指定列排序,这个版本的代码通过了优化,比QRowTable表格控件-支持hover整行、checked整行、指定列排序等这篇文章中将的版本要更优雅一些。

  1. 排序图标

纯白版使用的是Qt排序图标

腹黑版排序图标使咱们本身去绘制的,而且绘制水平表头时文本有特殊处理

  1. 列内容对其方式

看到这里的同窗不放本身也能够先思考下,看这3种需求的实现方式,有了大体思路后在继续往下看。

这样带着思路看,理解起来应该更加的容易。

5、指定列排序

还记得上一篇文章QRowTable表格控件-支持hover整行、checked整行、指定列排序等中怎么禁用指定列排序吗?忘记的同窗能够到这篇文章中去熟悉下,我记着应该是发现排序列不容许被排序时,直接禁用排序功能。

本篇文章咱们使用了一种更加优雅的方式来阻止指定列排序。

首先咱们重写了表头组件,而且重写了mouseReleaseEvent这个函数,由于这个函数中才触发了排序,所以这个函数中足以过滤掉不让排序的列。

class QRowHeader : public QHeaderView
{
    Q_OBJECT

public:
    QRowHeader(Qt::Orientation orientation, QWidget * parent = nullptr);

public:
    //设置是否支持排序
    void SetSortEnable(int logicalIndex, bool enable);

signals:
    void MouseMove();

protected:
    virtual void mouseMoveEvent(QMouseEvent * event) override;
    virtual void mouseReleaseEvent(QMouseEvent *e) override;
    virtual void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override;

private:
    QMap<int, bool> m_Indicator;
};

而后代码是这样处理的,代码量不大,就是判断鼠标点击的位置若是是禁止排序的列,咱们直接把事件循环中断啦!!!

是否是很坏,哈哈哈

void QRowHeader::mouseReleaseEvent(QMouseEvent * event)
{
    int column = logicalIndexAt(event->pos().x());
    if (m_Indicator.contains(column) && m_Indicator[column] == false)
    {
        return;
    }

    QHeaderView::mouseReleaseEvent(event);
}

6、排序

上边讲完了怎么去阻止排序,重写了QHeaderView。

virtual void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override;

里边有paintSection这样的函数,他就是绘制表头一个单元格所产生的回调。

在这个函数里边首先调用父窗口绘制表头,而后若是发现本身是排序列时,顺道去绘制一个排序图标.

代码很容易理解,绘制朝上仍是朝下的图标取决于咱们当前的排序方式

void QRowHeader::paintSection(QPainter * painter, const QRect & rect, int logicalIndex) const
{
    ...
    //绘制三角形
    if (sortIndicatorSection() == logicalIndex)
    {
        QRect indicator = rect;
        indicator.setLeft(indicator.right() - 6);
        indicator.setHeight(10);
        indicator.moveTop((rect.height() - indicator.height()) / 2);
        indicator.moveLeft(indicator.left() - 10);//距离左边界10像素
        if (sortIndicatorOrder() == Qt::AscendingOrder)
        {
            painter->drawPixmap(indicator, QPixmap(":/QRowTable/down_arrow.png.png"));
        }
        else if (sortIndicatorOrder() == Qt::DescendingOrder)
        {
            painter->drawPixmap(indicator, QPixmap(":/QRowTable/up_arrow.png.png"));
        }
    }
}

绘制列头中的项时,若是当前列是排序列,那么这里就须要绘制排序图标。若是恰好这一列的文字是右对齐呢,正常状况下咱们的图标还有文字多是没有问题的。

若是该列很窄,那么排序图标和列cell中的文字可能会重叠

既然有几率重叠,这里咱们就须要处理这个异常,当出现上述这种状况时,咱们须要改变原有的文字绘制区域,不让他在图标的绘制区域绘制文本。

仍是重写paintSection方法,这个是绘制列头一个单元格的方法。

看以下代码,首先咱们填充了这个cell,为何呢?不着急回答这个问题,接着往下看,咱们把原有的rect向左便宜了16个像素,而后绘制列头cell。

void QRowHeader::paintSection(QPainter * painter, const QRect & rect, int logicalIndex) const
{
    painter->fillRect(rect.adjusted(-1, 0, -1, -1), QColor("#212121"));

    painter->save();
    QRect r = rect;
    if (sortIndicatorSection() == logicalIndex)
    {
        r.adjust(0, 0, -16, 0);
    }
    QHeaderView::paintSection(painter, r, logicalIndex);
    painter->restore();

    painter->setPen(QColor("#2B2B2B"));
    painter->drawRect(rect.adjusted(-1, 0, -1, -1));
    
    ...

这样会有什么问题?仔细想一想,文字绘制没问题,其实有问题的是背景色绘制会发现错误

所以咱们在函数开头先把列头cell背景色进行了填充,这个背景色其实就是列头cell的背景色

setStyleSheet("QTableView{background:#333333;}"
        "QHeaderView{background:#212121;color:gray;}"
        "QHeaderView:section{padding:6px;border:0;background:#212121;color:gray;}");

上边是这个组件的qss样式,表头cell的背景色其实就是#2212121,所以填充区域咱们也使用了这个色值,最终达到咱们预期的效果。

这里插一句:绘制时必定要注意绘制的顺序,不然咱们自定义的绘制可能会和Qt本身的绘制有冲突。这里尽可能考虑清楚,省得产生冲突。

当列表头被点击时,QRowHeader对象会发出列cell被点击信号sectionClicked,这个信号也是从鼠标弹起函数mouseReleaseEvent中触发。

connect(m_pHHeader, &QRowHeader::sectionClicked, m_pFilter, &QFilterModel::setFilterKeyColumn);
connect(m_pHHeader, &QRowHeader::sectionClicked, this, [this](int column){
    if (column == 0 || column == 1 || column == 2)
    {
        GetFilterModel()->SetCompareType(QFilterModel::CT_INT);
    }
    else
    {
        GetFilterModel()->SetCompareType(QFilterModel::CT_STRING);
    }
});

sectionClicked信号触发后,这里干了两件事:设置当前的排序列和当前的排序方式。

SetCompareType接口用于设置当前排序方式。

目前这个组件支持3中排序方式,数字、百分比和字符串

bool QFilterModel::lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const
{
    if (m_eType == CT_INT)
    {
        double l = source_left.data().toDouble();
        double r = source_right.data().toDouble();

        return l < r;
    }
    else if (m_eType == CT_PERCENT)
    {
        double l = source_left.data(Qt::UserRole + 2).toString().remove("%").toDouble();
        double r = source_right.data(Qt::UserRole + 2).toString().remove("%").toDouble();
    
        return l < r;
    }
    else
    {
        QString l = source_left.data().toString();
        QString r = source_right.data().toString();

        return l < r;
    }
}

这里须要说明一下,百分比为何要单独拿出来分一类,设计之初,百分比和数字是放在一类中去比较的,可是后来发现百分比没有办法存储在double中,所以添加了百分比分类。这里也不强制非要添加一个新类,其余能实现比较需求的办法都可。

7、列对其方式

Qt的文字对其方式有以下这么多,而后咱们这个组件支持比较主流的集中排序方式,分别是:水平居左、水平居中、水平居右

水平居右时,若是当前列时排序列,咱们文字绘制的位置区域不能包含排序图标的大小,不然文字可能和图标重叠

enum AlignmentFlag {
    AlignLeft = 0x0001,
    AlignLeading = AlignLeft,
    AlignRight = 0x0002,
    AlignTrailing = AlignRight,
    AlignHCenter = 0x0004,
    AlignJustify = 0x0008,
    AlignAbsolute = 0x0010,
    AlignHorizontal_Mask = AlignLeft | AlignRight | AlignHCenter | AlignJustify | AlignAbsolute,

    AlignTop = 0x0020,
    AlignBottom = 0x0040,
    AlignVCenter = 0x0080,
    AlignBaseline = 0x0100,
    // Note that 0x100 will clash with Qt::TextSingleLine = 0x100 due to what the comment above
    // this enum declaration states. However, since Qt::AlignBaseline is only used by layouts,
    // it doesn't make sense to pass Qt::AlignBaseline to QPainter::drawText(), so there
    // shouldn't really be any ambiguity between the two overlapping enum values.
    AlignVertical_Mask = AlignTop | AlignBottom | AlignVCenter | AlignBaseline,

    AlignCenter = AlignVCenter | AlignHCenter
};

控件以外,咱们经过SetAlignment接口设置了该列的排序方式,排序方式对列的内容和列头都起做用。也就是说列头和列内容排序方式是一致的。

model->SetAlignment(0, Qt::AlignRight | Qt::AlignVCenter);
model->SetAlignment(1, Qt::AlignRight | Qt::AlignVCenter);
model->SetAlignment(2, Qt::AlignRight | Qt::AlignVCenter);
model->SetAlignment(3, Qt::AlignLeft | Qt::AlignVCenter);
model->SetAlignment(4, Qt::AlignLeft | Qt::AlignVCenter);
model->SetAlignment(5, Qt::AlignLeft | Qt::AlignVCenter);

列头绘制时,会经过headerData接口拿文字对其方式,而后这里只须要返回以前使用SetAlignment接口设置的对其方式便可。

QVariant QRowModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const
{
    if (Qt::TextAlignmentRole == role)
    {
        //Q::AlignLeft | Qt::AlignVCenter

        auto iter = m_AlignmentList.find(section);
        if (iter != m_AlignmentList.end())
        {
            return iter->second;
        }
        return Qt::AlignCenter;
        //return m_AlignmentList.at(section);
    }

    return QStandardItemModel::headerData(section, orientation, role);
}

补充

从新换了一种方式实现第五节的mouseReleaseEvent函数。

以前的方法在开启了列拖拽以后会出现问题

void QRowHeader::mouseReleaseEvent(QMouseEvent * event)
{
    int column = logicalIndexAt(event->pos().x());
    if (m_Indicator.contains(column) && m_Indicator[column] == false)
    {
        setSectionsClickable(false);
        QHeaderView::mouseReleaseEvent(event);
        setSectionsClickable(true);
    }
    else
    {
        QHeaderView::mouseReleaseEvent(event);
    }
}

8、相关文章

  1. Qt实现表格控件-支持多级列表头、多级行表头、单元格合并、字体设置等

  2. Qt高仿Excel表格组件-支持冻结列、冻结行、内容自适应和合并单元格

  3. 属性浏览器控件QtTreePropertyBrowser编译成动态库(设计师插件)

  4. 超级实用的属性浏览器控件--QtTreePropertyBrowser

  5. Qt之表格控件蚂蚁线

  6. QRowTable表格控件-支持hover整行、checked整行、指定列排序等





若是您以为文章不错,不妨给个 打赏,写做不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!














很重要--转载声明

  1. 本站文章无特别说明,皆为原创,版权全部,转载时请用连接的方式,给出原文出处。同时写上原做者:朝十晚八 or Twowords

  2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时经过修改本文达到有利于转载者的目的。

相关文章
相关标签/搜索