這是上一篇文章的續篇,關於自定義View。 多個View內部能夠映射到同一個數據模型,也能夠映射不一樣的數據結構;可使用全部數據,也能夠只使用部分數據。由於視圖層與數據層的分離,操做相對比較靈活。git
這裏咱們來實現一個自定義View,住要包含一下幾個功能:github
鼠標
點擊:鼠標點擊格子區域,繪製高亮的內方格,點擊非格子區域無反應。右箭頭(→)
按鍵:點擊選中格子後,按右箭頭按鍵移動到下一個格子,並選中。嗯,大概就是下面設計圖的樣子,而且咱們把左上角、右上角、右下角和左下角的數據分別約定爲QModelIndex(0,0)、QModelIndex(0,1)、QModelIndex(1,0)和QModelIndex(11)。數據結構
與Model子類化同樣,編寫自定義視圖的時候也有兩種選擇:繼承QAbstractItemView、繼承Qt提供的標準View。相對來講,繼承抽象基類QAbstractItemView須要花較多的功夫,繼承標準View類則比較快。當本身要使用的視圖類與某個標準View相近的時候,繼承這個View而且重寫本身須要的功能函數是最方便的。函數
Qt提供了一下幾個標準View(與標準Model相對應):this
其中QColumnView比較特殊,它是一個多列視圖,每一列是一個QListView,點擊前一列的某項會觸發下一列的內容內變:設計
這裏咱們選擇抽象基類,僅僅。。。。。出於隨意(-_-, 嘿嘿)。3d
經過繼承虛基類來實現自定義View主要有兩步:實現純虛函數、重載須要的虛函數。指針
自定義基類,第一步是要讓你的View運行起來,咱們須要實現全部的純虛函數。所以咱們須要實現如下函數:code
// pure virtuals virtual QRect visualRect(const QModelIndex &index) const; virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); virtual QModelIndex indexAt(const QPoint &point) const; virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); virtual int horizontalOffset() const; virtual int verticalOffset() const; virtual bool isIndexHidden(const QModelIndex &index) const; virtual void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command); virtual QRegion visualRegionForSelection(const QItemSelection &selection) const;
visualRect()
函數返回模型下標表明的項佔據的視圖範圍,若是該項包含多個區域,則返回主區域範圍。該函數將數據模型與視圖範圍相關聯。基本上,View的做用就是爲了渲染Model數據顯示,既然涉及到渲染,那渲染的範圍必然是很是重要的。下面是我自定義View的現實部分(以防萬一還進行了無效下標的檢測,並返回無效範圍):blog
QRect CustomeView::visualRect(const QModelIndex &index) const { if (!index.isValid()) return QRect(); int row = index.row(); int column = index.column(); if (row == 0) { if (column == 0) return m_leftTopSquare; if (column == 1) return m_rightTopSquare; } if (row == 1) { if (column == 0) return m_rightBottomSquare; if (column == 1) return m_leftBottomSquare; } return QRect(); }
indexAt()
,當用戶鼠標點擊的時候,咱們須要判斷它涉及那部分數據,這個函數就是用來處理這方面的邏輯的。這裏我簡單判斷了點是否包含在四個格子裏,是的話返回相應的數據下標;不然返回無效下標:
// used when users clicks at the client area QModelIndex CustomeView::indexAt(const QPoint &point) const { Q_ASSERT(this->model() != nullptr); if (m_leftTopSquare.contains(point)) return this->model()->index(0, 0); if (m_rightTopSquare.contains(point)) return this->model()->index(0, 1); if (m_rightBottomSquare.contains(point))return this->model()->index(1, 0); if (m_leftBottomSquare.contains(point)) return this->model()->index(1, 1); return QModelIndex(); }
moveCursor()
,雖然這個函數名是Cursor,但好像跟光標沒有關係,只設計鍵盤的操做,咱們的按鍵功能就在這裏實現。簡而言之,這個函數處理鍵盤上的移動按鍵(多個箭頭按鍵、Home鍵、End鍵等),返回下一個選中項。咱們只處理右箭頭:
QModelIndex CustomeView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { Q_ASSERT(this->model() != nullptr); if (cursorAction != QAbstractItemView::MoveRight || !m_currentModelIndex.isValid()) return QModelIndex(); else { auto row = m_currentModelIndex.row(); auto column = m_currentModelIndex.column(); if (row == 0) { if (column == 0) return this->model()->index(0, 1); if (column == 1) return this->model()->index(1, 0); } if (row == 1) { if (column == 0) return this->model()->index(1, 1); if (column == 1) return this->model()->index(0, 0); } } }
setSelection()
也處理選中項的問題,可是函數傳入的是一個範圍Rect和一個選中命令(選中、取消選中等)。用戶點擊時,也會調用這個函數。跟你想的不同,這時候傳入的不是一個點,也是一個範圍。鼠標點擊時,或多或少也會有移動,這就造成了一個小型的矩形範圍(□)。 經過判斷這個Rect是否包含在某個格子裏面,咱們作出相應的選中操做(或什麼都不作),完成鼠標點擊選中功能:
void CustomeView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) { if (m_leftTopSquare.contains(rect)) m_currentModelIndex = this->model()->index(0, 0); else if (m_rightTopSquare.contains(rect)) m_currentModelIndex = this->model()->index(0, 1); else if (m_rightBottomSquare.contains(rect)) m_currentModelIndex = this->model()->index(1, 0); else if (m_leftBottomSquare.contains(rect)) m_currentModelIndex = this->model()->index(1, 1); if (m_currentModelIndex.isValid()) this->selectionModel()->select(m_currentModelIndex, command); }
其餘純虛函數咱們不關心,直接返回了一個隨意值。
到這裏咱們只剩自定義的圖案尚未畫,顯而易見,咱們在painrEvent()
裏面實現。須要注意的是,咱們其實是在viewport
上畫圖,而不是在QAbstractItemView上,因此咱們要把viewport指針傳給QPainter:
void CustomeView::paintEvent(QPaintEvent *event) { QPainter painter(this->viewport()); if (this->model() != nullptr) { updateOneRect(painter, m_leftTopSquare, 0xff22ff, this->model()->index(0, 0)); updateOneRect(painter, m_rightTopSquare, 0xff2244, this->model()->index(0, 1)); updateOneRect(painter, m_rightBottomSquare, 0x999911, this->model()->index(1, 0)); updateOneRect(painter, m_leftBottomSquare, 0x992244, this->model()->index(1, 1)); } if (this->selectionMode()) { auto selectedIndexes = this->selectionModel()->selectedIndexes(); for (auto index : selectedIndexes) { if (index == this->model()->index(0, 0)) painter.fillRect(m_leftTopSquare.adjusted(20, 20, -20, -20), Qt::cyan); if (index == this->model()->index(0, 1)) painter.fillRect(m_rightTopSquare.adjusted(20, 20, -20, -20), Qt::cyan); if (index == this->model()->index(1, 0)) painter.fillRect(m_rightBottomSquare.adjusted(20, 20, -20, -20), Qt::cyan); if (index == this->model()->index(1, 1)) painter.fillRect(m_leftBottomSquare.adjusted(20, 20, -20, -20), Qt::cyan); } } QAbstractItemView::paintEvent(event); }
完整代碼見此處。