QT繪圖之圖形視圖
在Qt中我們可以通過QWidget的派生類,重寫paintEvent()函數,使用QPainter繪制我們想要的任何內容。這種方法對于從QWidget派生的窗口部件很理想,但如果要繪制大量獨立的項,向用戶提供與圖形項交互(如鼠標交互等),通常需要大量的工作。
圖形視圖架構,則完美地滿足了基于圖形項的高性能繪制和交互的需求。
在Qt4中,圖形視圖基于Qt中GUI和QtCore模塊,該架構的設計取代并超越了Qt3的QCanvas。它提供了一個平臺,用于大量自定義2D圖元的管理與交互,使用一個BSP(Binary Space Partitioning - 二叉空間分割)樹,以提供對圖形元素的快速查找。正因如此,它可以使超大的場景實時地可視化,即使其包含數百萬的圖元。
框架包括一個事件傳播架構,支持場景(Scene)中的圖元(Item)進行精確的雙精度交互功能。圖元可以處理鍵盤事件、鼠標按下、移動、釋放和雙擊事件,同時也能跟蹤鼠標移動。
1
圖形視圖架構
圖形視圖的 場景、視圖、 圖元,作為圖形視圖架構的三要素。其中每個圖元從抽象類(QGraphicsItem)派生而來,場景(QGraphicsScene)作為模型容納管理各個繪制項,視圖(QGraphicsView) 用來可視化顯示數據,如有需要,我們可以在多個不同的視圖內顯示同一場景。
場景
QGraphicsScene提供了圖形視圖場景。它提供了一個快速的接口,用于管理大量圖元、向每個圖元傳遞事件、管理圖元的狀態(tài),如:選中、焦點處理等。
場景是QGraphicsItem對象的容器。通過調用QGraphicsScene::addItem()將圖元添加到場景中后,你就可以通過調用場景中的不同的查找函數來查找其中的圖元。
QGraphicsScene::items()函數及其重載函數可返回所有圖元。包括:點、矩形等;通過QGraphicsScene::itemAt()接口返回在特定點上最上面的圖元。所有找到的圖元按照層疊遞減的排列順序(即:最先返回的圖元是最頂層的,最后返回的則是最底層的)。
QGraphicsScene的事件傳遞機制負責將場景事件傳遞給圖元,同時也管理圖元之間的傳遞。如果場景在某個位置得到一個鼠標按下事件,就將該事件傳遞給這個位置上的圖元。QGraphicsScene同時還管理某些圖元的狀態(tài),例如:圖元的選中和焦點。
視圖
提供一個可視的窗口,用于顯示場景中的圖元。對于同一個場景,可以提供多個不同/相同的視圖來顯示其元素。
QGraphicsView是可滾動的窗口部件,用來提供滾動條以瀏覽大的場景。如需使用OpenGL,可用QGraphicsView::setViewport()將視圖設置為QGLWidget。如果你希望OpenGL具有反鋸齒,則需要OpenGL支持采樣緩沖。視圖的類層次圖如下圖:

圖元
QGraphicsItem是場景中圖元的基類。其中提供了一些典型形狀的標準圖元,例如:矩形 、橢圓 、文本項 。當然你也可以自定義圖元項。除此之外,QGraphicsItem還支持以下特性:鼠標按下、移動、釋放和雙擊事件,以及鼠標懸浮事件、滾輪事件和上下文菜單事件;鍵盤輸入焦點和鍵盤事件;拖放;分組:通過父子關系或QGraphicsItemGroup;碰撞檢測。
每個圖元項都有一個z值,z值較大的項會被繪制在z值較小的項之上,這樣就可以調整覆蓋圖元項的顯示順序;每個圖元項可以是場景或者另一個項的子對象,當對一個項進行了矩陣變換,該項的所有子對象會自動的應用該變換,遞歸應用至最深層次的子孫對象。
如果讓子項忽略所有父項的變換,可以通過調用QGraphicsItem::setFlag(QGraphicsItem::ItemIgnoresTransformations)來實現(xiàn)。Qt中圖元項的類層次結構如下圖所示(具體用法請參見Qt幫助文檔):


2
圖形視圖坐標系
圖形視圖基于笛卡兒坐標系,場景中圖元的位置和幾何形狀由兩組數據來表示:X坐標和Y坐標。當使用一個未轉換的視圖來觀察一個場景,場景中的一個單元將會由場景上的一個像素表示。圖形視圖中使用了三種有效的坐標系: 圖元坐標、 場景坐標、 視圖坐標。
圖元坐標
圖元的繪制在自己的局部坐標系。它們的坐標通常圍繞它們的中心點(0, 0),并且這也是所有轉換的中心。創(chuàng)建自定義圖元時,只需考慮圖元坐標即可。QGraphicsScene和QGraphicsView會為你實現(xiàn)所有相關的轉換,這樣一來,實現(xiàn)自定義圖元就容易多了。圖元綁定的矩形(boundingRect())或形狀區(qū)域(shape())也是項坐標系統(tǒng)的。
子坐標是相對于父坐標而言的,如果子坐標沒有轉換,那么子坐標和父坐標的差異就和圖元在父坐標中的距離一樣,圖元的位置(pos)是圖元的中心點在其父坐標系下的坐標,有時也被稱為父坐標。
如果這個圖元直接加到場景中則它的位置在場景坐標中,由于圖元的位置和轉換是相對于父圖元來說的,因此,雖然父圖元的轉換隱式地轉換了子圖元,但是子圖元的坐標不會因其轉換而受影響。不過相對于場景來說,子圖元將隨著父圖元進行轉換和偏移 。圖元坐標系如下圖所示:

場景坐標系
場景為所有圖元提供了基礎坐標系。場景坐標系描述了每個頂層圖元的位置,同時構成了從視圖傳遞到場景中所有事件的基礎。其中每個圖元都有一個場景位置以及坐標系下的矩形邊界(QGraphicsItem::scenePos()QGraphicsItem::sceneBoundingRect()),場景坐標系如下圖所示:

視圖坐標系
視圖坐標是窗口的坐標,視圖坐標中的每個單位對應一個像素。對于該坐標系來說較特殊的一點是:它相對于視口,不會受被觀察的場景所影響。QGraphicsView以窗口的左上角作為自己坐標系的原點總是(0, 0),右下角總是(width, height)。所有的鼠標事件和拖拽事件都以視圖坐標接收到的,你需要將這些坐標映射到場景,以便于和圖元進行交互。視圖坐標系如下圖:

坐標映射

3
事 件
當QGraphicsView接收到Qt鼠標、鍵盤和拖放事件(QMouseEvent、QKeyEvent、QDragEvent等)時,它會將其轉換為QGraphicsceneEvent子類的實例,并轉發(fā)到它顯示的QGraphicscene。然后,場景再將事件轉發(fā)到相關圖元項,即:視圖->場景->圖元。圖形視圖中事件的類層次如下:

4
動 畫
圖形視圖在幾個層面上提供了對動畫的支持。實現(xiàn)圖元動畫有三種方法:
1) 使用Qt中的Animation Framework框架,QPropertyAnimation可以為任何QObject屬性實現(xiàn)動畫效果,所以你的圖元必須繼承QObject和QGraphicsItem。
2) 創(chuàng)建一個自定義圖元,從QObjcet和QGraphicsItem繼承,該圖元可以通過定時來驅動實現(xiàn)動畫。
3) 調用QGraphicsScene::advance()從而依次調用QGraphicsItem::advance();
5
示 例
自定義圖元示例

構建一個場景視圖示例
向場景中添加了一個矩形,一個圓:

* 注: 本文介紹基于Qt4.8.6.