Qt--自定義View

這是上一篇文章的續篇,關於自定義View。 多個View內部能夠映射到同一個數據模型,也能夠映射不一樣的數據結構;可使用全部數據,也能夠只使用部分數據。由於視圖層與數據層的分離,操做相對比較靈活。git

1. 實現一個自定義View

這裏咱們來實現一個自定義View,住要包含一下幾個功能:github

  1. 圖案自定義:包含四個顏色不一的格子,大小小於整個窗口的1/4, 分別位於窗口的四個角落。
  2. 響應鼠標點擊:鼠標點擊格子區域,繪製高亮的內方格,點擊非格子區域無反應。
  3. 響應右箭頭(→)按鍵:點擊選中格子後,按右箭頭按鍵移動到下一個格子,並選中。

嗯,大概就是下面設計圖的樣子,而且咱們把左上角右上角右下角左下角的數據分別約定爲QModelIndex(0,0)、QModelIndex(0,1)、QModelIndex(1,0)和QModelIndex(11)。數據結構

design-img

1.1 選擇合適的視圖類繼承

與Model子類化同樣,編寫自定義視圖的時候也有兩種選擇:繼承QAbstractItemView、繼承Qt提供的標準View。相對來講,繼承抽象基類QAbstractItemView須要花較多的功夫,繼承標準View類則比較快。當本身要使用的視圖類與某個標準View相近的時候,繼承這個View而且重寫本身須要的功能函數是最方便的。函數

Qt提供了一下幾個標準View(與標準Model相對應):this

  • QListView。
  • QTableView。
  • QTreeView。
  • QColumnView。
  • QHeaderView:用來提供表頭視圖給其餘View使用。

其中QColumnView比較特殊,它是一個多列視圖,每一列是一個QListView,點擊前一列的某項會觸發下一列的內容內變:設計

qcolumnview-img

這裏咱們選擇抽象基類,僅僅。。。。。出於隨意(-_-, 嘿嘿)。3d

1.2 繼承虛基類2步走

經過繼承虛基類來實現自定義View主要有兩步:實現純虛函數重載須要的虛函數指針

1.2.1 實現QAbstractItemView的純虛函數

自定義基類,第一步是要讓你的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);
}

其餘純虛函數咱們不關心,直接返回了一個隨意值。

1.2.2 重載功能須要的虛函數

到這裏咱們只剩自定義的圖案尚未畫,顯而易見,咱們在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);
}

運行結果

result1-img result2-img

完整代碼見此處

相關文章
相關標籤/搜索