QTableView表格控件區域選擇-自繪選擇區域

原文連接:QTableView表格控件區域選擇-自繪選擇區域瀏覽器

1、開心一刻

陪完客戶回到家,朦朧之中,看到我媽正在拖地,我掏出200塊塞到我媽手裏,說道:媽,給你點零花錢,別讓我媳婦知道。緩存

我媽接過錢,大吼:你是否是又喝酒了?ide

我:噓,你怎麼知道的?函數

老媽:你看清楚了,我是你媳婦,還有。這200塊錢是哪來的,說!我:啊……學習

2、概述

最近優化了一個小功能,主要是模仿excel相關的操做,以爲還挺不錯的,所以在這裏進行了整理,分享給有須要的朋友。今天主要是說一下區域選擇這項功能,Qt自帶的表格控件是具備區域選擇功能的,可是他並不美觀,不能支持咱們自定義邊框色和一些細節上的調整。字體

今天博主就來說解下本身是怎麼自定義這個區域選擇功能的。優化

主要使用的方式仍是自繪,下面先來看下效果,是否是你想要的。this

3、效果展現

以下圖所示,是一個自繪選擇區域的效果展現,除此以外demo中還有一些其餘的效果,但不是本篇文章所要講述的內容。spa

本篇文章的重點就是講述怎麼實現區域選擇框繪製

4、實現思路

看過效果圖以後,接下來開始分析怎麼繪製矩形選擇框。下面以問題的形式來進行分析,這樣更有利於理解。

那麼先來思考以下幾個很問題

  1. 怎麼肯定繪製區域
  2. 怎麼肯定繪製的邊框
  3. 誰去繪製更好

以上三個問題搞懂了,那麼今天的主要內容也就差很少了。

一、繪製區域

學習Qt的第一步即是看幫助文檔,不得不說Qt的幫助文檔那是作的至關好,很是齊全。既然如此那還等什麼,直接打開Qt 助手看看以下幾個類都有哪些信號把。

QTableView

//QAbstractItemView
void activated(const QModelIndex &index)
void clicked(const QModelIndex &index)
void doubleClicked(const QModelIndex &index)
void entered(const QModelIndex &index)
void iconSizeChanged(const QSize &size)
void pressed(const QModelIndex &index)
void viewportEntered()

QTableView是表格控件基類,咱們的表格也是基於這個控件進行開發。再看這個類的包含的信號(其中都是他的父窗口信號),對於本小結開始提出的3個問題好像沒有特別大的做用。那麼咱們繼續往下看,看看他的數據存儲類。

QStandardItemModel

void itemChanged(QStandardItem *item)

//parent QAbstractItemModel

void columnsAboutToBeInserted(const QModelIndex &parent, int first, int last)
void columnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn)
void columnsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
void columnsInserted(const QModelIndex &parent, int first, int last)
void columnsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column)
void columnsRemoved(const QModelIndex &parent, int first, int last)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int> ())
void headerDataChanged(Qt::Orientation orientation, int first, int last)
void layoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex> (), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoLayoutChangeHint)
void layoutChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex> (), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoLayoutChangeHint)
void modelAboutToBeReset()
void modelReset()
void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
void rowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow)
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
void rowsInserted(const QModelIndex &parent, int first, int last)
void rowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
void rowsRemoved(const QModelIndex &parent, int first, int last)

QStandardItemModel即是QTableView的數據模型了,一眼掃過好像都是模型數據發生變化了的一些信號。這個時候發現M和V好像沒有咱們須要的東西,Qt不會真這麼挫吧。答案固然是「否」,仔細翻閱Qt的幫助文檔就會發現QAbstractItemView類能夠返回一個selectionModel,看其名字好像是咱們須要的東西。

QItemSelectionModel * selectionModel() const

隨繼續翻閱幫助文檔,咱們獲得如下信息

void currentChanged(const QModelIndex &current, const QModelIndex &previous)
void currentColumnChanged(const QModelIndex &current, const QModelIndex &previous)
void currentRowChanged(const QModelIndex &current, const QModelIndex &previous)
void modelChanged(QAbstractItemModel *model)
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)

哈哈哈,果真找到了咱們須要的信號,看信號名稱就知道,當前項發生變化時觸發,而後咱們就能夠去統計哪些項被選中。

到這裏,咱們的第一個問題就算回答了,咱們能夠經過selectionModel的selectionChanged信號來統計可能須要繪製border的單元格。

//鏈接信號
connect(m_pVew->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ExcTableWidget::SelectionChanged);

二、繪製邊框

信號鏈接上後,開始處理信號。

思路大體是這樣的:

  1. 使用gridCell記錄全部的單元格
  2. 循環遍歷選中的單元格
  3. 判斷當前單元格哪一個邊是須要繪製的
  4. 結果存儲於gridPosints結構中

判斷邏輯也比較簡單,邏輯比較簡單,能夠直接看代碼。這裏我舉一個例子,好比說是否須要繪製左border,那麼就是須要看這個cell左邊是否有cell,或者本身已是第一列。

gridPosints是QMap<QModelIndex, QVector >類型,鍵存儲單元格索引,值存儲4個邊的狀態(是否須要繪製)

void ExcTableWidget::SelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
    QModelIndexList indexs = m_pVew->selectionModel()->selectedIndexes();

    qDebug() << indexs;

    int row = GetModel()->rowCount();
    int column = GetModel()->columnCount();

    QVector<QVector<bool>> gridCell(row, QVector<bool>(column));

    for each (const QModelIndex & index in indexs)
    {
        gridCell[index.row()][index.column()] = true;
    }

    QMap<QModelIndex, DrawTypes> datas;
    QMap<QModelIndex, QVector<GridPoint>> gridPosints;
    for each (const QModelIndex & index in indexs)
    {
        DrawTypes types;
        bool topLine = true, rightLine = true, bottomLine = true, leftLine = true;
        if (index.row() == 0)
        {
            types |= TOP;
        }
        else
        {
            int aboveCell = index.row() - 1;
            if (gridCell[aboveCell][index.column()] == false)
            {
                types |= TOP;
            }
            else
            {
                topLine = false;
            }
        }

        if (index.column() == GetModel()->columnCount() - 1)
        {
            types |= RIGHT;
        }
        else
        {
            int rightCell = index.column() + 1;
            if (gridCell[index.row()][rightCell] == false)
            {
                types |= RIGHT;
            }
            else
            {
                rightLine = false;
            }
        }

        if (index.row() == GetModel()->rowCount() - 1)
        {
            types |= BOTTOM;
        }
        else
        {
            int beloveCell = index.row() + 1;
            if (gridCell[beloveCell][index.column()] == false)
            {
                types |= BOTTOM;
            }
            else
            {
                bottomLine = false;
            }
        }

        if (index.column() == 0)
        {
            types |= LEFT;
        }
        else
        {
            int leftCell = index.column() - 1;
            if (gridCell[index.row()][leftCell] == false)
            {
                types |= LEFT;
            }
            else
            {
                leftLine = false;
            }
        }

        datas[index] = types;

        gridPosints[index].push_back({ TOP, topLine });
        gridPosints[index].push_back({ RIGHT, rightLine });
        gridPosints[index].push_back({ BOTTOM, bottomLine });
        gridPosints[index].push_back({ LEFT, leftLine });
    }

    m_pVew->SetCellDatas(gridPosints);
    SelectStyle * style = m_pVew->GetDelegate();
    style->SetCellDatas(datas);

    m_pVew->update();
}

到這裏,咱們的第二個問題就算回答了,咱們須要繪製邊框的單元格總算是計算出來了。

三、繪製

數據都有了,繪製還會遠嗎?

接下來繼續往下看,Qt提供的繪製邏輯機制仍是很強大滴,咱們能夠經過如下方式重繪

一、重寫QStyledItemDelegate

QStyledItemDelegate是繪圖代理,大多數的繪製操做最終都會在這裏被執行,看參數就知道每個cell繪製時都會來這裏。

virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override;

可是這裏有一個問題,那就是這個函數可繪製的區域問題,只能在這個cell裏邊繪製,若是繪製在border上將會被覆蓋,不信看以下堆棧。

繪圖代理QStyledItemDelegate的paint函數是被QTableView的paintEvent函數進行回調。

既然繪圖代理中繪製cell項時不能繪製到cell外邊去,那麼恰好,咱們能夠在這裏進行選擇區域的填充

void SelectStyle::DrawSelected(QPainter * painter, const QRect & rect, const QModelIndex & index) const
{
    if (m_indexs.contains(index) == false)
    {
        return;
    }

    painter->save();

    QPen pen = painter->pen();
    pen.setWidth(1);
    pen.setColor(m_color);
    painter->setPen(pen);

    painter->fillRect(rect, QColor(100, 0, 0, 100));

    painter->restore();
}

填充完選擇區域後,接下來即是繪製選擇區域的border。

二、重寫paintEvent
看了函數調用堆棧後,你們內心應該也比較清楚QTableView是怎麼繪製的了吧。既然繪製代理不能完成需求,那麼咱們就只能在paintEvent這座大山中進行繪製。

這裏須要注意一點就是,咱們須要先試用QTableView自己的paintEvent把原有的繪製走一遍,保證界面上的信息都是全的,而後在執行咱們本身的定製代碼。

以下圖所示,父類的paintEvent函數執行完畢後,咱們繪製了border邊線






以前在selectionModel的selectionChanged信號中,咱們已經獲取到了須要繪製border的cell信息,下面繪製時只須要根據緩存數據繪製便可,看這代碼很長,但速度槓槓滴。

void FreezeTableView::paintEvent(QPaintEvent * event)
{
    QTableView::paintEvent(event);

    //繪製網格線
    QPainter painter(viewport());
    painter.save();
    QPen pen = painter.pen();
    pen.setWidth(1);
    pen.setColor(m_pSelectBorder->GetLineColor());
    painter.setPen(pen);

    for (auto iter = m_indexs.begin(); iter != m_indexs.end(); ++iter)
    {
        QModelIndex index = iter.key();
        QVector<GridPoint> cellTyeps = iter.value();
        QRect rect = visualRect(index);
        QRect tmpRect = rect;
        tmpRect.adjust(-1, -1, 1, 1);
        if (index.column() == 0)
        {
            tmpRect.adjust(1, 0, 0, 0);
        }
        if (index.row() == 0)
        { 
            tmpRect.adjust(0, 1, 0, 0);
        }

        for (int i = 0; i < cellTyeps.size(); ++i)
        {
            const GridPoint & point = cellTyeps.at(i);

            if (point.type == TOP && point.line)
            {
                painter.drawLine(tmpRect.topLeft(), tmpRect.topRight());
            }
            if (point.type == RIGHT && point.line)
            {
                painter.drawLine(tmpRect.topRight(), tmpRect.bottomRight());
            }
            if (point.type == BOTTOM && point.line)
            {
                painter.drawLine(tmpRect.bottomLeft(), tmpRect.bottomRight());
            }
            if (point.type == LEFT && point.line)
            {
                painter.drawLine(tmpRect.topLeft(), tmpRect.bottomLeft());
            }
        }
    }

    for (auto iter = m_indexsBorder.begin(); iter != m_indexsBorder.end(); ++iter)
    {
        QModelIndexList indexs = iter.key();
        for each (const QModelIndex & index in indexs)
        {
            QRect rect = visualRect(index);
            rect.adjust(-1, -1, 0, 0);
            if (index.column() == 0)
            {
                rect.adjust(1, 0, 0, 0);
            }
            if (index.row() == 0)
            {
                rect.adjust(0, 1, 0, 0);
            }
            painter.setPen(iter.value());
            painter.drawRect(rect);
        }
    }

    painter.restore();
}

有了以上核心代碼,自繪選擇區域的功能基本上也就能夠實現了。

5、相關文章

  1. Qt實現表格控件-支持多級列表頭、多級行表頭、單元格合併、字體設置等

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

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

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

  5. Qt之表格控件螞蟻線

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

  7. QRowTable表格控件(二)-紅漲綠跌


值得一看的優秀文章:

  1. 財聯社-產品展現
  2. 廣聯達-產品展現
  3. Qt定製控件列表
  4. 牛逼哄哄的Qt庫





若是您以爲文章不錯,不妨給個 打賞,寫做不易,感謝各位的支持。您的支持是我最大的動力,謝謝!!!














很重要--轉載聲明

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

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

相關文章
相關標籤/搜索