原文連接:QTableView表格控件區域選擇-自繪選擇區域瀏覽器
陪完客戶回到家,朦朧之中,看到我媽正在拖地,我掏出200塊塞到我媽手裏,說道:媽,給你點零花錢,別讓我媳婦知道。緩存
我媽接過錢,大吼:你是否是又喝酒了?ide
我:噓,你怎麼知道的?函數
老媽:你看清楚了,我是你媳婦,還有。這200塊錢是哪來的,說!我:啊……學習
最近優化了一個小功能,主要是模仿excel相關的操做,以爲還挺不錯的,所以在這裏進行了整理,分享給有須要的朋友。今天主要是說一下區域選擇這項功能,Qt自帶的表格控件是具備區域選擇功能的,可是他並不美觀,不能支持咱們自定義邊框色和一些細節上的調整。字體
今天博主就來說解下本身是怎麼自定義這個區域選擇功能的。優化
主要使用的方式仍是自繪,下面先來看下效果,是否是你想要的。this
以下圖所示,是一個自繪選擇區域的效果展現,除此以外demo中還有一些其餘的效果,但不是本篇文章所要講述的內容。spa
本篇文章的重點就是講述怎麼實現區域選擇框繪製
看過效果圖以後,接下來開始分析怎麼繪製矩形選擇框。下面以問題的形式來進行分析,這樣更有利於理解。
那麼先來思考以下幾個很問題
以上三個問題搞懂了,那麼今天的主要內容也就差很少了。
學習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 ¤t, const QModelIndex &previous) void currentColumnChanged(const QModelIndex ¤t, const QModelIndex &previous) void currentRowChanged(const QModelIndex ¤t, 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);
信號鏈接上後,開始處理信號。
思路大體是這樣的:
判斷邏輯也比較簡單,邏輯比較簡單,能夠直接看代碼。這裏我舉一個例子,好比說是否須要繪製左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(); }
有了以上核心代碼,自繪選擇區域的功能基本上也就能夠實現了。
值得一看的優秀文章:
![]() |
![]() |
很重要--轉載聲明