目錄html
原文連接:Qt高仿Excel表格組件-支持凍結列、凍結行、內容自適應和合並單元格瀏覽器
最近看到一個比較炫酷的表格效果,凍結表格列功能。常用excel的人應該都使用過這個功能,當咱們想把一些重要的信息一直固定在界面上時,就得使用凍結行或者凍結列的功能。app
以前我也作過相似的凍結列的功能,並且Qt的源碼中也有相似的demo。ide
對Qt比較熟悉的人應該都知道,Qt的安裝包裏能夠爲咱們安裝不少的Qt使用事例,都很是不錯,很值得學習。我我的也是常常會去學習其中的東西,建議你們沒事也多看看。函數
Qt自帶的有一個事例程序,工程名字就作frozencolumn,這個功能就演示了怎麼去實現凍結列的功能,思路很是不錯。因而,我也借鑑這個想法,作了好幾個複雜控件,都是使用這個思路來實現的效果,後續陸續放出佈局
像標題說的那樣,本篇文章咱們不只僅是實現凍結列的功能,除此以外,凍結行、內容自適應行高,單元格合併這些咱們都要須要完成。學習
下面這張圖展現了凍結行、凍結列效果。gif圖有點兒長,能夠花點兒時間看完,確認是本身想要的效果,再繼續往下看。測試
Qt中的demofrozencolumn
是怎麼幹的字體
既然Qt中已經幫咱們是想了凍結列的功能,那麼久先來分析下這個demo吧。this
實現列凍結,也就是說在拖動水平滾動條的時候,第一列永遠顯示在窗口上。 怎麼作到這個效果呢?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(); } }
大概思路就講這麼多了,其餘一些細節的實現你們能夠自行完成,有困難能夠找我。
編輯單元格內容時,行高自適應。
直接調用我封裝好的類,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); } }
螞蟻線這個工我以前在另外一篇文章中有講過,須要重寫一個繪圖代理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; };
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; } } } }
//測試凍結列 tableView->SetFrozen(2, 2);
//測試行高 tableView->SetRowHight(2, 100); //測試列寬 tableView->SetColumnWidth(1, 200);
//設置單元格文本顏色 第一行前五列字體爲紅色 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);
//設置單元格背景色 第二行前五列背景色爲紅色 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);
//合併單元格 tableView->SetSpan(5, 5, 2, 2); //自適應第一行高度 tableView->ResizeRowHeight(0); //高度自適應 儘可能放在大量數據填充完 修改數據階段啓用 tableView->ResizeRowHeightEnable(true); //選擇框顏色和樣式 tableView->SetFoucsLine(Qt::red, false); tableView->SetMotionLine(true);
Qt自帶的demo中有一個demo程序,源碼工程叫作frozencolumn。
這個控件就是依賴於這個二次開發的,固然了已經被我封裝成了一個控件,對外暴露的都是藉口,用戶不在須要關心內容的實現邏輯了。只須要調用幾個接口,就能夠達到這樣的效果。
後續還會依次放出其餘複雜控件:
很重要--轉載聲明