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. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時經過修改本文達到有利於轉載者的目的。

相關文章
相關標籤/搜索