碼迷,mamicode.com
首頁 > 其他好文 > 詳細

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

時間:2019-07-11 17:31:57      閱讀:29      評論:0      收藏:0      [點我收藏+]

標簽:spl   rri   eve   就會   mouse   setw   slots   demo   app极速时时彩走势图   

原文鏈接:

一、概述

极速时时彩走势图最近在研究QTableView支持多級表頭的事情,百度了下網上資料還是挺多的。實現的方式總的來說有2種,效果都還不錯,最主要是搞懂其中的原理,做到以不變應萬變。

實現多級表頭的方式有以下兩種方案

  1. 行表頭和列表頭都是用一個表格去模擬
  2. 重寫QHeadView

以上兩種方式都可以實現多級表頭,各有利弊,并且已經有人投入項目使用。

我個人還是比較偏向于第二種方式,因為這樣我們才可以更好的了解Qt的底層,了解Qt的繪圖機制,并且這樣實現的效率也是比較高的,而且合理一些,比較可控(個人理解)。

后來我在網上找到了一個哥們寫的控件,項目名字叫做RbTableHeaderView,挺不錯的,可以實現我們要的功能,但是效果還是差一些,如果需要更友好的交互效果,那么還需要在繼續完善這個demo。

极速时时彩走势图今天閑來無事,找到了一個開源的網站,上邊好多Qt的庫,雖然有一些是很早以前的東西,但是也很值得我們去學習。為什么會提到這個網站呢?因為這個網站上就有我們要的這個多級表頭事例,和上邊提到的那個哥們的事例不謀而合。

极速时时彩走势图想要學習更多開源事例的可以到上去看看。還有我自己收錄的

下面我們就來講解這個多節表頭的實現方式,代碼比較簡單,主要是大家理解下這個實現方式,可以加以擴展。

后續的文章中我會在寫一篇關于樹控件多級表頭的事例,這里先把文章名稱掛載這里,后續發布后就可以看到--

二、效果展示

极速时时彩走势图多級表頭的效果下圖所示,很糙粗的一個demo,大家將就著看吧。

技術圖片

三、定制表頭

定制表頭我們主要是要重寫2個東西,分別是數據源QAbstractTableModel和表頭QHeaderView

1、重寫數據源

數據源就是為視圖提供數據的model,我們的所有顯示的內容數據都來自這個model。

极速时时彩走势图對于外部程序填充數據時和往常使用同樣的方式

for (int i = 0; i < 10; i++)
{
    QList<QStandardItem*> items;
    for (int j = 0; j < 8; j++)
    {
        items.append(new QStandardItem(QString("item(%1, %2)").arg(i).arg(j)));
    }
    dataModel->appendRow(items);
}

极速时时彩走势图重寫了這個數據源后,我們主要是為了完成data的返回數據過程,View最關心的就是這個接口

class RbTableHeaderModel : public QAbstractTableModel
{
    Q_OBJECT

public:
    // override
    virtual QVariant data(const QModelIndex &index, int role) const;

private:
    // properties
    int row_count_prop;
    int column_count_prop;
    // inherent features
    RbTableHeaderItem* root_item;
};

极速时时彩走势图下面就是data的函數實現,是不是大失所望,所有的額操作好像被封裝到RbTableHeaderItem這個節點中去了。

QVariant RbTableHeaderModel::data(const QModelIndex & index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= row_count_prop || index.row() < 0 || index.column() >= column_count_prop || index.column() < 0)
        return QVariant();

    RbTableHeaderItem * item = static_cast<RbTableHeaderItem *>(index.internalPointer());

    return item->data(role);
}

RbTableHeaderItem結構表示了一個單元格,而且他還維護了所有的表格cell子節點。

注意看下面index的構造,把index和RbTableHeaderItem這個結構綁定在了一起。

index中的很多數據也都存儲在了RbTableHeaderItem這個結構中,后續我們在講視圖的時候大家就會發現了。

极速时时彩走势图Model也就這么多內動了,View才是我們的重頭戲。

QModelIndex RbTableHeaderModel::index(int row, int column, const QModelIndex & parent) const
{
    RbTableHeaderItem * parentItem;
    if (!parent.isValid())
        parentItem = root_item; // parent item is always the root_item on table model
    else 
        parentItem = static_cast<RbTableHeaderItem*>(parent.internalPointer()); // no effect

    RbTableHeaderItem * childItem = parentItem->child(row, column);
    if (!childItem) 
        childItem = parentItem->insertChild(row, column);
    return 
        createIndex(row, column, childItem);

    return QModelIndex();
}

2、重寫QHeaderView

重寫表頭時,公有接口用于設置單元格行高、列寬、背景色和前景色的,單元格合并等。

保護接口都是重寫父類的方法,在合適的實際會被框架進行調用。

inherent features注釋下的方法是自己封裝的方法,方便其他函數調用。

class RbTableHeaderView : public QHeaderView
{
    void setRowHeight(int row, int rowHeight);
    void setColumnWidth(int col, int colWidth);
    void setSpan(int row, int column, int rowSpanCount, int columnSpanCount);
    void setCellBackgroundColor(const QModelIndex & index, const QColor &);
    void setCellForegroundColor(const QModelIndex & index, const QColor &);

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

protected Q_SLOTS:
    void onSectionResized(int logicalIdx, int oldSize, int newSize);

Q_SIGNALS:
    void sectionPressed(int from, int to);
};

下面我們分析幾個比較重要的函數

1、mousePressEvent鼠標按下

极速时时彩走势图當鼠標按下時mousePressEvent函數被觸發,然后我們需要去計算那個單元格被按下了,并通知視圖,讓視圖去選擇某些cell集合。

极速时时彩走势图這個函數的處理邏輯會比較負責一些,這個dmeo做的有問題,這里我就不按照demo中的代碼來講解了。

首先我們還是得根據自己的需求來實現這個鼠標按下事件,對于大多數的程序來說,可能都是鼠標按下時,選中視圖中的單元格集合,那么我們這里也就按照這個思路來分析。

如下圖所示,我們在程序加載過程中,給表頭頭設置了合并屬性,對于合并了的表格項,他們對象的index中都是存儲了紅色文字信息的。

技術圖片

當我們點擊了某一個item時,程序就需要去判斷是否點擊了這個大的合并sell,然后去選擇tableview視圖上的cell集合。

就是這么簡單,但是實現起來還是有一定難度的。

思路就到這里了,具體邏輯大家可以去思考下。

2、paintSection繪制函數

UI上真正的繪制函數其實就是paintSection函數,當這個函數回調的時候,我們只需要在程序給定的區域內繪制上文本即可,那么問題來了,這個區域是這么計算出來的,既然我們要合并了列和行,那么每一個區域的大小應該都是不一樣的。

分析的一點都沒錯,這個區域的大小Qt已經幫我們留好了接口--sectionSizeFromContents

我們只需要重寫這個函數即可,根據我們之前保存的index上合并列和行的數據進行計算,計算出一個合適的區域,然后把值返回即可。

QSize RbTableHeaderView::sectionSizeFromContents(int logicalIndex) const
{
    const RbTableHeaderModel * tblModel = qobject_cast<const RbTableHeaderModel*>(this->model());
    const int OTN = orientation();
    const int LEVEL_CNT = (OTN == Qt::Horizontal) ? tblModel->rowCount() : tblModel->columnCount();

    QSize siz = QHeaderView::sectionSizeFromContents(logicalIndex);
    for (int i = 0; i < LEVEL_CNT; ++i)
    {
        QModelIndex cellIndex = (OTN == Qt::Horizontal) ? tblModel->index(i, logicalIndex) : tblModel->index(logicalIndex, i);
        QModelIndex colSpanIdx = columnSpanIndex(cellIndex);
        QModelIndex rowSpanIdx = rowSpanIndex(cellIndex);
        siz = cellIndex.data(Qt::SizeHintRole).toSize();

        if (colSpanIdx.isValid())
        {
            int colSpanFrom = colSpanIdx.column();
            int colSpanCnt = colSpanIdx.data(COLUMN_SPAN_ROLE).toInt();
            int colSpanTo = colSpanFrom + colSpanCnt - 1;
            siz.setWidth(columnSpanSize(colSpanIdx.row(), colSpanFrom, colSpanCnt));
            if (OTN == Qt::Vertical) i = colSpanTo;
        }
        if (rowSpanIdx.isValid())
        {
            int rowSpanFrom = rowSpanIdx.row();
            int rowSpanCnt = rowSpanIdx.data(ROW_SPAN_ROLE).toInt();
            int rowSpanTo = rowSpanFrom + rowSpanCnt - 1;
            siz.setHeight(rowSpanSize(rowSpanIdx.column(), rowSpanFrom, rowSpanCnt));
            if (OTN == Qt::Horizontal) i = rowSpanTo;
        }
    }
    return siz;
}

3、列大小改變

當手動拖拽列帶下時,onSectionResized槽函數會被調用,然后我們需要在這個函數中把相鄰的列頭大小進行重新設置。

void RbTableHeaderView::onSectionResized(int logicalIndex, int oldSize, int newSize)
{
    for (int i = 0; i < LEVEL_CNT; ++i)
    {

        QSize cellSize = cellIndex.data(Qt::SizeHintRole).toSize();
        // set position of cell
        if (OTN == Qt::Horizontal)
        {
            sectionRect.setTop(rowSpanSize(logicalIndex, 0, i));
            cellSize.setWidth(newSize);
        }
        else
        {
            sectionRect.setLeft(columnSpanSize(logicalIndex, 0, i));
            cellSize.setHeight(newSize);
        }
        tblModel->setData(cellIndex, cellSize, Qt::SizeHintRole);


        QRect rToUpdate(sectionRect);
        rToUpdate.setWidth(viewport()->width() - sectionRect.left());
        rToUpdate.setHeight(viewport()->height() - sectionRect.top());
        viewport()->update(rToUpdate.normalized());
    }
}

大致的實現思路就是這樣的,由于核心實現代碼邏輯比較長,大多數的代碼我只保留了關鍵的執行步驟。

四、設置屬性

极速时时彩走势图下面這一大堆代碼看似很長,其實很好理解,就是調用我們封裝好的控件進行設置

  1. 第一段設置了水平表頭合并和內容
  2. 第二段設置了垂直表頭合并和內容
  3. 第三段設置水平表頭行高
  4. 第四段設置了垂直表頭列寬和行高
  5. 第五段設置水平和垂直表頭可點擊
  6. 第六段設置水平和垂直表頭背景色
hHead->setSpan(0, 0, 3, 0);
hHead->setSpan(0, 1, 2, 2);
hHead->setSpan(1, 3, 2, 0);
hModel->setData(hModel->index(0, 0), QStringLiteral("一級表頭"), Qt::DisplayRole);
hModel->setData(hModel->index(0, 1), QStringLiteral("一級表頭"), Qt::DisplayRole);
hModel->setData(hModel->index(2, 1), QStringLiteral("二級表頭"), Qt::DisplayRole);
hModel->setData(hModel->index(2, 2), QStringLiteral("二級表頭"), Qt::DisplayRole);
hModel->setData(hModel->index(0, 3), QStringLiteral("一級表頭"), Qt::DisplayRole);
hModel->setData(hModel->index(1, 3), QStringLiteral("二級表頭"), Qt::DisplayRole);

vHead->setSpan(0, 0, 0, 3);
vHead->setSpan(1, 0, 3, 0);
vHead->setSpan(1, 1, 2, 0);
vModel->setData(vModel->index(0, 0), QStringLiteral("一級表頭"), Qt::DisplayRole);
vModel->setData(vModel->index(1, 0), QStringLiteral("一級表頭"), Qt::DisplayRole);
vModel->setData(vModel->index(1, 1), QStringLiteral("二級表頭"), Qt::DisplayRole);
vModel->setData(vModel->index(3, 1), QStringLiteral("二級表頭"), Qt::DisplayRole);
vModel->setData(vModel->index(1, 2), QStringLiteral("三級表頭"), Qt::DisplayRole);
vModel->setData(vModel->index(2, 2), QStringLiteral("三級表頭"), Qt::DisplayRole);
vModel->setData(vModel->index(3, 2), QStringLiteral("三級表頭"), Qt::DisplayRole);

hHead->setRowHeight(0, 30);
hHead->setRowHeight(1, 30);
hHead->setRowHeight(2, 30);

vHead->setRowHeight(0, 30);
vHead->setRowHeight(1, 30);
vHead->setRowHeight(2, 30);
vHead->setColumnWidth(0, 50);
vHead->setColumnWidth(1, 50);
vHead->setColumnWidth(2, 50);

hHead->setSectionsClickable(true);
vHead->setSectionsClickable(true);

hHead->setCellBackgroundColor(hModel->index(0, 0), 0xcfcfcf);
hHead->setCellBackgroundColor(hModel->index(0, 1), 0xcfcfcf);
vHead->setCellBackgroundColor(vModel->index(0, 0), Qt::cyan);
vHead->setCellBackgroundColor(vModel->index(1, 0), 0xcfcfcf);

這個demo網上也可以下載到,或者去我文章開頭留下的開源庫地址去拿也行。

現在的CSDN不敢恭維了,代碼也沒地方放了。代碼如果下載不到也可以留言我來發。。。

五、相關文章





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








技術圖片 技術圖片






很重要--轉載聲明

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

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


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

標簽:spl   rri   eve   就會   mouse   setw   slots   demo   app   

原文地址:https://www.cnblogs.com/swarmbees/p/11167664.html

(0)
(0)
   
舉報
評論 一句話評論(0
0條  
登錄后才能評論!
           
? 2014 mamicode.com 版權所有 京ICP備13008772號-2
迷上了代碼!