QRowTable表格控制元件(二)-紅漲綠跌

朝十晚八發表於2019-07-18

原文連結:QRowTable表格控制元件(二)-紅漲綠跌

一、開心一刻

一天,五娃和六娃去跟蛇精決鬥,決鬥前有這樣一段對話。

五娃:“妖精!今天我倆就要消滅你!今天就是你的死期!”

蛇精:“呵呵呵,真是可笑。你們自己個兒都是從樹上長出來的,憑什麼叫我妖精?!”

五娃:“你也說了,我們是從樹上長出來的,是葫蘆變的,自然不是妖精。”

蛇精:“你們不是妖精,難道還是神仙了,再難不成你把自己當人了?”

五娃和六娃異口同聲道:“哈哈哈哈哈哈!你說對了,我就是人,是植!物!人!”

二、概述

最近工作比較忙,不過還是抽時間把這個表格元件繼續完善下去。

Qt的自帶的表格控制元件非常強大,支援各種方便操作,上一篇文章QRowTable表格控制元件-支援hover整行、checked整行、指定列排序等介紹了一個簡單的demo,主要是做一個股票表格控制元件,而他天然的就是支援hover和checked行特性,對Qt比較熟悉的同學可能都知道Qt其實有兩個介面,可以設定互動行為為選擇行。

介面就是這兩個貨了。

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. 最重要的時候我們自己還是實現了一堆的小需求,下一小節我們來一個個分析

三、效果展示

以下是紅漲綠跌效果圖,實現功能一樣。

UI展現形式不一樣,但是都實現了紅漲綠跌

  1. 背景色不同
  2. 排序效果不同

純白版



QRowTable表格控制元件(二)-紅漲綠跌


腹黑版



QRowTable表格控制元件(二)-紅漲綠跌


四、任務需求

看過效果圖之後,有沒有發現我們這裡的表格控制元件和Qt自帶的控制元件有什麼區別呢?其實這裡我們要達到gif展示的那種效果,還是使用了很多實現技巧。

控制元件都包含哪些功能呢?

  1. 指定列排序

指定列排序,這個版本的程式碼經過了優化,比QRowTable表格控制元件-支援hover整行、checked整行、指定列排序等這篇文章中將的版本要更優雅一些。

  1. 排序圖示

純白版使用的是Qt排序圖示

腹黑版排序圖示使我們自己去繪製的,並且繪製水平表頭時文字有特殊處理

  1. 列內容對其方式

看到這裡的同學不放自己也可以先思考下,看這3種需求的實現方式,有了大致思路後在繼續往下看。

這樣帶著思路看,理解起來應該更加的容易。

五、指定列排序

還記得上一篇文章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);
}

六、排序

上邊講完了怎麼去阻止排序,重寫了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中,因此新增了百分比分類。這裡也不強制非要新增一個新類,其他能實現比較需求的辦法均可。

七、列對其方式

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);
}

八、相關文章

  1. Qt實現表格控制元件-支援多級列表頭、多級行表頭、單元格合併、字型設定等

  2. Qt高仿Excel表格元件-支援凍結列、凍結行、內容自適應和合並單元格

  3. 屬性瀏覽器控制元件QtTreePropertyBrowser編譯成動態庫(設計師外掛)

  4. 超級實用的屬性瀏覽器控制元件--QtTreePropertyBrowser

  5. Qt之表格控制元件螞蟻線

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





如果您覺得文章不錯,不妨給個打賞,寫作不易,感謝各位的支援。您的支援是我最大的動力,謝謝!!!








QRowTable表格控制元件(二)-紅漲綠跌 QRowTable表格控制元件(二)-紅漲綠跌






很重要--轉載宣告

  1. 本站文章無特別說明,皆為原創,版權所有,轉載時請用連結的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords

  2. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。


相關文章