Qt實現表格控制元件-支援多級列表頭、多級行表頭、單元格合併、字型設定等
目錄
- 一、概述
- 二、效果展示
- 三、定製表頭
- 1、重寫資料來源
- 2、重寫QHeaderView
- 四、設定屬性
- 五、相關文章
原文連結:Qt實現表格控制元件-支援多級列表頭、多級行表頭、單元格合併、字型設定等
一、概述
最近在研究QTableView支援多級表頭的事情,百度了下網上資料還是挺多的。實現的方式總的來說有2種,效果都還不錯,最主要是搞懂其中的原理,做到以不變應萬變。
實現多級表頭的方式有以下兩種方案
- 行表頭和列表頭都是用一個表格去模擬
- 重寫QHeadView
以上兩種方式都可以實現多級表頭,各有利弊,並且已經有人投入專案使用。
我個人還是比較偏向於第二種方式,因為這樣我們才可以更好的瞭解Qt的底層,瞭解Qt的繪圖機制,並且這樣實現的效率也是比較高的,而且合理一些,比較可控(個人理解)。
後來我在網上找到了一個哥們寫的控制元件,專案名字叫做RbTableHeaderView,挺不錯的,可以實現我們要的功能,但是效果還是差一些,如果需要更友好的互動效果,那麼還需要在繼續完善這個demo。
今天閒來無事,找到了一個開源的網站,上邊好多Qt的庫,雖然有一些是很早以前的東西,但是也很值得我們去學習。為什麼會提到這個網站呢?因為這個網站上就有我們要的這個多級表頭事例,和上邊提到的那個哥們的事例不謀而合。
想要學習更多開源事例的可以到openDesktop上去看看。還有我自己收錄的牛逼哄哄的Qt庫
下面我們就來講解這個多節表頭的實現方式,程式碼比較簡單,主要是大家理解下這個實現方式,可以加以擴充套件。
後續的文章中我會在寫一篇關於樹控制元件多級表頭的事例,這裡先把文章名稱掛載這裡,後續釋出後就可以看到--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());
}
}
大致的實現思路就是這樣的,由於核心實現程式碼邏輯比較長,大多數的程式碼我只保留了關鍵的執行步驟。
四、設定屬性
下面這一大堆程式碼看似很長,其實很好理解,就是呼叫我們封裝好的控制元件進行設定
- 第一段設定了水平表頭合併和內容
- 第二段設定了垂直表頭合併和內容
- 第三段設定水平表頭行高
- 第四段設定了垂直表頭列寬和行高
- 第五段設定水平和垂直表頭可點選
- 第六段設定水平和垂直表頭背景色
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不敢恭維了,程式碼也沒地方放了。程式碼如果下載不到也可以留言我來發。。。
五、相關文章
- Qt實現表格樹控制元件-支援多級表頭
- Qt實現高仿excel表格-可執行檔案(原始碼不開放)
- Qt高仿Excel表格元件-支援凍結列、凍結行、內容自適應和合並單元格
- 牛逼哄哄的Qt庫
- openDesktop
如果您覺得文章不錯,不妨給個打賞,寫作不易,感謝各位的支援。您的支援是我最大的動力,謝謝!!!
很重要--轉載宣告
本站文章無特別說明,皆為原創,版權所有,轉載時請用連結的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords
如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。