目錄
原文連結:Qt高仿Excel表格元件-支援凍結列、凍結行、內容自適應和合並單元格
一、概述
最近看到一個比較炫酷的表格效果,凍結表格列功能。經常使用excel的人應該都使用過這個功能,當我們想把一些重要的資訊一直固定在介面上時,就得使用凍結行或者凍結列的功能。
之前我也做過類似的凍結列的功能,而且Qt的原始碼中也有類似的demo。
對Qt比較熟悉的人應該都知道,Qt的安裝包裡可以為我們安裝很多的Qt使用事例,都非常不錯,很值得學習。我個人也是經常會去學習其中的東西,建議大家沒事也多看看。
Qt自帶的有一個事例程式,工程名字就做frozencolumn,這個功能就演示了怎麼去實現凍結列的功能,思路非常不錯。於是,我也借鑑這個想法,做了好幾個複雜控制元件,都是使用這個思路來實現的效果,後續陸續放出
像標題說的那樣,本篇文章我們不僅僅是實現凍結列的功能,除此之外,凍結行、內容自適應行高,單元格合併這些我們都要需要完成。
二、效果展示
下面這張圖展示了凍結行、凍結列效果。gif圖有點兒長,可以花點兒時間看完,確認是自己想要的效果,再繼續往下看。
三、實現思路
1、凍結行、凍結列
Qt中的demofrozencolumn
是怎麼幹的
既然Qt中已經幫我們是想了凍結列的功能,那麼久先來分析下這個demo吧。
實現列凍結,也就是說在拖動水平滾動條的時候,第一列永遠顯示在視窗上。 怎麼做到這個效果呢?Qt給的解決辦法很簡單,我們只需要把兩個檢視疊加在一起,上層這個檢視只顯示第一列,下層的檢視是全顯示,然後拖動的時候我們只需要正常拖動下層的這個檢視即可。
是不是很簡單呢。Qt封裝的控制元件,介面都很齊全,我們只需要使用connect把相關的變化繫結起來即可。
setModel(model);
frozenTableView = new QTableView(this);
init();
//connect the headers and scrollbars of both tableviews together
connect(horizontalHeader(),&QHeaderView::sectionResized, this,
&FreezeTableWidget::updateSectionWidth);
connect(verticalHeader(),&QHeaderView::sectionResized, this,
&FreezeTableWidget::updateSectionHeight);
connect(frozenTableView->verticalScrollBar(), &QAbstractSlider::valueChanged,
verticalScrollBar(), &QAbstractSlider::setValue);
connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
frozenTableView->verticalScrollBar(), &QAbstractSlider::setValue);
上述程式碼是從Qt5.7.1_vs2013版本中複製出來的。
看到了吧,就是這麼簡單。
下面就是我們自己封裝凍結列、凍結行的講解,思路參考Qt的。
我們自己的高仿Excel表格
既然Qt都這麼幹了,我們還有什麼理由不這麼幹呢?
話不多說,直接開幹,既然要凍結列和行,那我們至少還需要在新增2個上層檢視,以固定列和行。
第一個版本的結構就是這樣的,多了2個上層檢視。
QTableView * m_pFrozenLeftTopView;
QTableView * m_pFrozenRowView;
等程式做好後,發現一個問題,上層2個用來凍結列和凍結行的檢視,永遠只有一個是工作正常的,2個上層檢視疊加的區域總是出現問題。
要麼水平滾動正常,要麼垂直滾動正常
思考了很久,為什麼呢?也想了很多辦法去解決這個問題,最後還是決定,在新增一個檢視到行和列檢視重疊的區域,因為這麼做最簡單。
至於為什麼,大家可以自己想想,這裡我就不做結束了,語言不是特別好描述,感覺自己也描述不清楚,囧。。。。
最後呢,我們的上層檢視列表會像下面這樣,從名字應該也可以看到他們分別是幹什麼的。
QTableView * m_pFrozenLeftTopView;
QTableView * m_pFrozenRowView;
QTableView * m_pFrozenColumnView;
然後就是建構函式了,負責同步他們之間的狀態
//沒有佈局 因此必須把父視窗帶上
m_pFrozenLeftTopView = new QTableView(this);
m_pFrozenColumnView = new QTableView(this);
m_pFrozenRowView = new QTableView(this);
init();
connect(horizontalHeader(), &QHeaderView::sectionResized, this,
&FreezeTableView::updateSectionWidth);
connect(verticalHeader(), &QHeaderView::sectionResized, this,
&FreezeTableView::updateSectionHeight);
//垂直檢視垂直滾動條 -> 垂直滾動條
connect(m_pFrozenColumnView->verticalScrollBar(), &QAbstractSlider::valueChanged,
verticalScrollBar(), &QAbstractSlider::setValue);
//垂直滾動條 -> 垂直檢視垂直滾動條
connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
m_pFrozenColumnView->verticalScrollBar(), &QAbstractSlider::setValue);
//水平滾動條 -> 水平檢視水平滾動條
connect(horizontalScrollBar(), &QAbstractSlider::valueChanged,
m_pFrozenRowView->horizontalScrollBar(), &QAbstractSlider::setValue);
connect(m_pFrozenRowView->horizontalScrollBar(), &QAbstractSlider::valueChanged,
horizontalScrollBar(), &QAbstractSlider::setValue);
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);
connect(m_pFrozenColumnView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);
connect(m_pFrozenRowView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);
connect(m_pFrozenLeftTopView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);
4個檢視上的當前選中項維護是一個比較費勁的操作,我這裡設定了每個檢視都只能選中一個單元格,然後其他檢視單元格被選中的時候,清空其他三個檢視上的當前選中項。
當某一個檢視被點選時,updateSelections槽就會被處罰。然後根據引數中被選中項,獲取點選的單元格行號和列號,依次拿到被點選了的檢視,接著清空其他沒有被點選的檢視當前選中項。
void FreezeTableView::updateSelections(const QItemSelection & selected, const QItemSelection &)
{
if (selected.isEmpty())
{
return;
}
QModelIndex index = selected.indexes().at(0);
int row = index.row();
int column = index.column();
if (row < m_iRowFrozen
&& column < m_iColumnFrozen)//左上
{
clearSelection();
m_pFrozenRowView->clearSelection();
m_pFrozenColumnView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
}
else if (row >= m_iRowFrozen
&& column < m_iColumnFrozen)//左下
{
clearSelection();
m_pFrozenRowView->clearSelection();
m_pFrozenLeftTopView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
}
else if (row >= m_iRowFrozen
&& column >= m_iColumnFrozen)//右下
{
m_pFrozenColumnView->clearSelection();
m_pFrozenRowView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
m_pFrozenLeftTopView->clearSelection();
}
else if (row < m_iRowFrozen
&& column >= m_iColumnFrozen)//右上檢視點選
{
selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
m_pFrozenColumnView->clearSelection();
m_pFrozenLeftTopView->clearSelection();
}
}
大概思路就講這麼多了,其他一些細節的實現大家可以自行完成,有困難可以找我。
2、行高自適應
編輯單元格內容時,行高自適應。
直接呼叫我封裝好的類,ResizeRowHeightEnable介面,引數傳遞true即可。
程式碼比較簡單,一看應該都可以明白。是用過表格resizeRowToContents這個介面自適應行高。
void ExcTableWidget::ResizeRowHeightEnable(bool enable)
{
if (enable)
{
connect(m_pModel, &QStandardItemModel::itemChanged, m_pVew, [this](QStandardItem * item){
m_pVew->resizeRowToContents(item->row());
}, Qt::UniqueConnection);
}
else
{
m_pModel->disconnect(m_pVew);
}
}
3、螞蟻線
螞蟻線這個工我之前在另一篇文章中有講過,需要重寫一個繪圖代理QStyledItemDelegate,然後設定給表格控制元件。
可以參考Qt之表格控制元件螞蟻線這篇文章。
下面是這個繪圖代理的標頭檔案,其中有幾個public介面,主要是用於設定螞蟻線的樣式,和是否啟用螞蟻線的。
其中比較重要的介面是paint虛擬函式,這個函式裡邊對單元格進行了繪製。
很重要:我們的繪製函式一定不要忘記呼叫原來的paint函式,否則單元格的其他樣式都會丟失
class SelectStyle : public QStyledItemDelegate
{
Q_OBJECT
public:
SelectStyle(QObject * parent = nullptr) : QStyledItemDelegate(parent), m_bAntLine(false), m_iOffset(0), m_color(0, 132, 255){}
~SelectStyle(){}
public:
void GoStepAntLine(bool);
void SetLineColor(const QColor & color);
void SetLineType(bool dash);
protected:
virtual void paint(QPainter * painter
, const QStyleOptionViewItem & option
, const QModelIndex & index) const override;
private:
void DrawBorderRect(QPainter * painter, const QRect & rect, bool firstColumn) const;
void DrawDashRect(QPainter * painter, const QRect & rect, bool firstColumn) const;
protected:
bool m_bAntLine;
bool m_bDashState;
int m_iOffset;
QColor m_color;
};
四、測試程式碼
1、新增表格資料
QFile file(":/grades.txt");
if (file.open(QFile::ReadOnly)) {
QString line = file.readLine(200);
QStringList list = line.simplified().split(',');
tableView->SetHeaderLabels(list);
QStringList lines;
while (file.canReadLine()) {
line = file.readLine(200);
lines.append(line);
}
file.close();
int i = 1;
int row = 0;
while (i-- > 0)
{
for each (const QString & line in lines)
{
if (!line.startsWith('#') && line.contains(',')) {
list = line.simplified().split(',');
for (int col = 0; col < list.length(); ++col){
tableView->SetItemData(row, col, list.at(col));
}
++row;
}
}
}
}
2、設定凍結行、列
//測試凍結列
tableView->SetFrozen(2, 2);
3、行高、列寬
//測試行高
tableView->SetRowHight(2, 100);
//測試列寬
tableView->SetColumnWidth(1, 200);
4、單元格背景色
//設定單元格文字顏色 第一行前五列字型為紅色
tableView->SetItemForegroundColor(0, 0, Qt::red);
tableView->SetItemForegroundColor(0, 1, Qt::red);
tableView->SetItemForegroundColor(0, 2, Qt::red);
tableView->SetItemForegroundColor(0, 3, Qt::red);
tableView->SetItemForegroundColor(0, 4, Qt::red);
5、單元格文字
//設定單元格背景色 第二行前五列背景色為紅色
tableView->SetItemBackgroundColor(1, 0, Qt::red);
tableView->SetItemBackgroundColor(1, 1, Qt::red);
tableView->SetItemBackgroundColor(1, 2, Qt::red);
tableView->SetItemBackgroundColor(1, 3, Qt::red);
tableView->SetItemBackgroundColor(1, 4, Qt::red);
//設定單元格文字對齊方式 第三行前五列文字居中
tableView->SetTextAlignment(2, 0, Qt::AlignCenter);
tableView->SetTextAlignment(2, 1, Qt::AlignCenter);
tableView->SetTextAlignment(2, 2, Qt::AlignCenter);
tableView->SetTextAlignment(2, 3, Qt::AlignCenter);
tableView->SetTextAlignment(2, 4, Qt::AlignCenter);
//設定單元格文字對齊方式 第四行前五列文字字型
QFont font;
font.setBold(true);//加粗
font.setPixelSize(18);//18畫素
font.setItalic(true);//斜體
font.setFamily(QString("Microsoft Yahei"));
font.setUnderline(true);//是否有下劃線
font.setStrikeOut(true);
tableView->SetItemFont(3, 0, font);
tableView->SetItemFont(3, 1, font);
tableView->SetItemFont(3, 2, font);
tableView->SetItemFont(3, 3, font);
tableView->SetItemFont(3, 4, font);
6、其他相關測試
//合併單元格
tableView->SetSpan(5, 5, 2, 2);
//自適應第一行高度
tableView->ResizeRowHeight(0);
//高度自適應 儘量放在大量資料填充完 修改資料階段啟用
tableView->ResizeRowHeightEnable(true);
//選擇框顏色和樣式
tableView->SetFoucsLine(Qt::red, false);
tableView->SetMotionLine(true);
五、相關文章
Qt自帶的demo中有一個demo程式,原始碼工程叫做frozencolumn。
這個控制元件就是依賴於這個二次開發的,當然了已經被我封裝成了一個控制元件,對外暴露的都是藉口,使用者不在需要關心內容的實現邏輯了。只需要呼叫幾個介面,就可以達到這樣的效果。
後續還會依次放出其他複雜控制元件:
如果您覺得文章不錯,不妨給個打賞,寫作不易,感謝各位的支援。您的支援是我最大的動力,謝謝!!!
很重要--轉載宣告
本站文章無特別說明,皆為原創,版權所有,轉載時請用連結的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords
如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。