1. 程式人生 > 實用技巧 >Qt 繪圖之QGraphicsScene QGraphicsView QGraphicsItem詳解

Qt 繪圖之QGraphicsScene QGraphicsView QGraphicsItem詳解

Graphics View提供了一個介面,它既可以管理大數量的定製2D graphical items,又可與它們互動,有一個view widget可以把這些項繪製出來,並支援旋轉與縮放。這個櫃架也包含一個事件傳播結構,對於在scene中的這些items,它具有雙精度的互動能力。Items能處理鍵盤事件,滑鼠的按,移動、釋放、雙擊事件,也可以跟蹤滑鼠移動。Graphics View使用BSP樹來提供對item的快速查詢,使用這種技術,它可以實時地繪製大規模場景,甚至以百萬items計。Graphics View在Qt 4.2中被引用,它替代了它的前輩QCanvas。

Graphics View的體系結構

Graphics View提供的是一種類似於Qt model-view的程式設計。多個views可以監視同一個場景,而場景包含多個具有多種幾何外形的items。

場景

QGraphicsScene 表示Graphics View中的場景,它有以下職責: 為管理大量的items提供一個快速的介面。 傳播事件到每個item。 管理item的狀態,例如選擇,焦點處理。 提供未經變換的渲染功能,主要用於列印。 場景作為QGraphicsItem物件的容器。通過呼叫QgraphicsScene::addItem()把這些Items加入到場景中。可以使用眾多的查詢函式來獲取特定的items。QGraphicsScene:items()與它的許多過載函式可獲取那些與點、矩形,多邊形,向量路徑等相交或是有包含有關係的items。QGraphicsScene::itemAt()返回特定上最頂端的item。所有的item查詢函式都以出棧序列返回(也就是說,第一個返回的是最頂端的,最後一個返回的是最底端的)。 QGraphicsScene scene; QGraphicsRectItem *rect=scene.addRect(QRectF(0,0,100,100)); QGraphicsItem *item=scene.itemAt(50,50); //item==rect; QGraphicsScene的事件傳播結構會把場景事件投遞到items,也管理多個items之間的傳遞。假如場景收到了滑鼠在某個位置press事件,場景會把這個事件投遞給處在那個位置的item。QGraphicsScene也管理某種item狀態,像選擇與焦點。你可以通過呼叫QGraphicsScene::setSelectionArea()來選擇items,它需要提供一個任意的形狀為引數。這個函式也作為在QGraphicsView實現橡皮筋選擇功能的一個基礎。為得到這些已經被選擇的items,呼叫QGraphicsScene::selectedItem()。另一個狀態處理是是否一個item擁有鍵盤輸入焦點。你可以呼叫QGraphicsScene::setFocusItem()或QGraphics::setFocus()來設定焦點,也可用QGraphicsScene::focusItem()來得到當前擁有焦點的那個item。最後,QGraphicsScene允許你通過呼叫QGraphicsScene::render()函式把部分場景送到繪圖裝置進行渲染。

檢視

QGraphicsView提供了檢視部件,它視覺化場景中的內容。你可以聯結多個檢視到同一個場景,對這個相同的資料集提供幾個視口。視口部件是一個滾動區域,它提供了滾動條以對大場景進行瀏覽。為了使用OpenGL,你應該呼叫QGraphicsView::setViewport()來把一個QGLWidget設為視口。檢視從鍵盤,滑鼠接收輸入事件,在傳送這些事件到場景之前,會對這些事件進行適當的翻譯(把事件座標轉換成對應的場景座標)。

利用轉換矩陣,QGraphicsView::matrix(),檢視可變換場景的座標系統。這允許高階的導航特性,如縮放,旋轉。為了方便,QGraphicsView也提供了在檢視與場景之間進行座標轉換的函式:QGraphicsView::mapToScene(),QGraphicsView::mapForScene()。

The Item QGraphicsItem 是場景中圖形items的基類。Graphics View 提供了一些標準的、用於典型形狀的items。像矩形(QGraphicsRectItem),橢圓(QGraphicsEllipseItem),文字(QGraphicsTextItem),當你寫定製的item時,那些最有用的一些QGraphicsItem特性也是有效的。除此這外,QGraphicsItem支援以下特性: *滑鼠按、移動、釋放、雙擊事件,滑鼠懸停事件,滾輪事件,彈出選單事件。 *鍵盤輸入焦點,鍵盤事件。 *拖拽 *組,包括父子關係,使用QGraphicsItemGroup *碰撞檢測 Items如同QGraphicsView一樣,位於本地座標系,它也為item與場景之間,item與item之間的座標轉換提供許多工具函式。而且,也像QGraphicsView一樣,它使用矩陣來變換它的座標系統:QGraphicsItem::matrix()。它對旋轉與縮放單個的Item比較有用。 Items可以包含別的items(孩子)。父items的轉換被它的子孫所繼承。然而,它的所有函式(也就是,QGraphicsItem::contains(),QGraphicsItem::boundingRect(),QGraphicsItem::collidesWith()),不會積累這些轉換,依然在本地座標下工作。 QGraphicsItem通過QGraphicsItem::shape(),QGraphicsItem::collideWith())來支援碰撞檢測。這兩個都是虛擬函式。從shape()返回你的item的形狀(以本地座標QPainterPath表示),QGraphicsItem會為你處理所有的碰撞檢測。假如你想提供自己的碰撞檢測,你應該重新實現QGraphicsItem::collideWith()。

Graphics View 座標系統

Graphics View基於笛卡爾座標系。item在場景中的位置與幾何形狀通過x,y座標表示。當使用未經變形的檢視來觀察場景時,場景中的一個單位等於螢幕上的一個畫素。在Graphics View中有三個有效的座標系統:Item座標系,場景座標系,檢視座標系。為了簡化你的實現,Graphics View提供了方便的函式,允許三個座標系之間相互對映。 當渲染時,Graphics View的場景座標對應於QPainter的邏輯座標,檢視座標與裝置座標相同。

Item座標

Items位於它們自己的座標系中。它的座標都以點(0,0)為中心點,這也是所有變換的中心點。在item座標系中的幾何圖元,經常被稱為item點,item線,item矩形。當建立一個定製的item,item座標是所需要考慮的。QGraphicsScene與QGraphicsView可以為你執行所有轉換,這使得實現定製的item變得容易。舉例來說,假如你收到滑鼠按或是拖進入事件,事件的位置以item座標的形式給出。QGraphicsItem::contain()虛擬函式,當某個點的位置在你的item範圍內時,返回true,否則返回false。這個點引數使用item座標,相似地,item的包圍矩形與形狀也使用item座標。 Item位置指的是item的中心點在它父親的座標系中的座標。以這種思想來看,場景指的就是那些祖先最少的item的“父親”。最上級的Item位置就是在場景中的位置。 子座標與父座標之間是相關的,假如孩子未經變換,子座標與父座標之間的差值等於在父座標系下,父item與子item之間的距離。例如,假如一個未經變換的子item位置與其父item的中心重合,那麼這兩個item的座標系統完全相同。如果孩子的位置是(10,0),那麼孩子座標系中的(0,10)點,對應於父座標系中的(10,10)點。 因為item的位置與變換是相對於父item的,子item的座標不會被父親的變換影響,儘管父item的變換隱含地對子item做了變換。在上面的例子中,即使父item旋轉,縮放,子item的(0,10)點依然對應於父item的(10,10)點。然而,相對於場景來講,子item會遵循父item的變換。假如父item被縮放(2X,2X),子item的位置在場景中的座標是(20,0),它的(10,0)點則與場景中的(40,0)對應 。除了QGraphicsItem::pos(),QGraphicsItem的函式以Item座標工作,如一個item’s包圍矩形總是以item座標的形式給出。

場景座標

場景座標系統描述了每個最頂級item的位置,也是從檢視向場景投遞場景事件的基礎。場景中的每個item有場景位置與包圍矩形(QGraphicsItem::scenePos(),QGraphicsItem::sceneBoundingRect()), 另外,它有自己本地item位置與包圍矩形。場景位置描述了item在場景座標下的位置,它的場景包圍矩形則用於QGraphicsScene決定場景中哪塊區域發生了變化。場景中的變化通過QGraphicsScene::changed()訊號來通知,它的引數是場景矩形列表。

檢視座標

檢視座標是widget的座標,檢視座標中每個單位對應一個畫素。這種座標的特殊之處在於它是相對於widget或是視口的,不會被所觀察的場景所影響。QGraphicsView的視口的左上角總是(0,0),右下角總是(視口寬,視口高)。所有的滑鼠事件與拖拽事件,最初以檢視座標表示,就應該把這些座標對映到場景座標以便與item互動。

座標對映

經常,處理場景中item時,在場景與item之間,item與item之間,檢視與場景之間進行座標對映,形狀對映是非常有用的。舉例來講,當你在QGraphicsView的視口中點選滑鼠時,你應該通過呼叫QGraphicsView::mapToScence()與QGraphicsScene::itemAt()來獲知游標下是場景中的哪個item。假如你想獲知一個item位於視口中的什麼位置,你應該先在item上呼叫QGraphicsItem::mapToScene(),然後呼叫QGraphicsView::mapFromScene()。最後,假如你想在一個檢視橢圓中有哪些items,你應該把QPainterPath傳遞到mapToScene(),然後再把對映後的路徑傳遞到QGraphicsScene::items()。 你可以呼叫QGraphicsItem::mapToScene()與QGraphicsItem::mapFromScene()在item與場景之間進行座標與形狀的對映。也可以在item與其父item之間通過QGraphicsItem::mapToParent()與QGraphicsItem::mapFromItem()進行對映。所有對映函式可以包括點,矩形,多邊形,路徑。檢視與場景之間的對映也與此類似。對於從檢視與item之間的對映,你應該首先對映到場景,然後再從場景向item進行對映。

關鍵特性

縮放與旋轉

QGraphicsView通過QGraphicsView::setMatrix()支援同QPainter一樣的仿射變換,通過對一個檢視應用變換,你可以很容易地支援普通的導航特性如縮放與旋轉。下面是一個例子: class View:;public QGraphicsView { Q_OBJECT //….. public slots: void zoomIn() {scale(1.2,1.2);} void zoomOut() {scale(1/1.2,1/1.2);} void rotateLeft() {rotate(-10);} void rotateRight() {rotate(10);} }; 這些槽應與QToolButtons聯接,並使autoRepeat有效。當對檢視變換時,QGraphicsView會對檢視中心進行校正。

拖拽

因為QGraphicsView繼承自QWidget,它也提供了像QWidget那樣的拖拽功能,另處,為了方便,Graphics View櫃架也為場景,每個item提供拖拽支援。當檢視接收到拖拽事件,它可翻譯為QGraphicsSceneDragDropEvent,再發送到場景。場景接管這個事件,把它傳送到游標下接受拖拽的第一個item。 從一個item開始拖拽時,建立一個QDrag物件,傳遞開始拖拽的那個widget的指標。Items可以同時被多個檢視觀察,但只有一個檢視可以開始拖拽。拖拽在多數情況下是從按下滑鼠或是移動滑鼠開始的,因此,在 mousePressEvent()或mouseMoveEvent()中,你可以從事件中得到那個原始的widget指標,例如: void CustomItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { QMimeData *data=new QMimeData; data->setColor(Qt::green); QDrag *drag=new QDrag(event->widget()); drag->setMimeData(data); drag->start(); } 為了在場景中載取拖拽事件,你應重新實現QGraphicsScene::dragEnterEvent()和在QGraphicsItem的子類裡任何與你特定場景需要的事件處理器。items也可以通過呼叫QGraphicsItem::setAcceptDrops()獲得拖拽支援,為了處理將要進行的拖拽,你需要重新實現QGraphicsItem::dragEnterEvent(),QGraphicsItem::dragMoveEvent(),QGraphicsItem::dragLeaveEvent()和QGraphicsItem::dropEvent()。

游標與工具提示

像QWidget一樣,QGraphicsItem也支援游標(QgraphicsItem::setCursor)與工具提示(QGraphicsItem::setToolTip())。當游標進入到item的區域,游標與工具提示被QGraphicsView啟用(通過呼叫QGraphicsItem::contains()檢測)。你也可以直接在檢視上設定一個預設游標(QGraphicsView::setCursor)。

動畫

Graphics View支援幾種級別的動畫。你可以很容易地通過把QGraphicsItemAnimatoin與你的item聯結來 裝配出動畫路徑,這允許以時間線來控制動畫,在所有平臺上以穩定的速率運作。QGraphicsItemAnimation允許你為item的位置,旋轉,縮放,剪下,變換等產生一條路徑,動畫可以用QSlider來控制,或更為普遍使用的QTimeLine。 另一種是從QObject和QGraphicsItem繼承,item可以設定自己的定時器,以在QObject::timeEvent()中增加步進的方式來控制動畫。 第三種,是通過呼叫QGraphicsScene::advance()來推進場景,它又依次呼叫QGraphicsItem::advance().

OpenGL渲染

為了使用OpenGL渲染,你要設定一個新的QGLWidget作為QGraphicsView的視口:QGraphicsView::setViewPort()。假如你讓OpenGL提供反鋸齒功能,你需要OpenGL取樣緩衝支援。 QGraphicsView view(&scene); view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));

Item組

通過把一個item做為另一個item的孩子,你可以得到item組的大多數本質特性:這些items會一起移動,所有變換 會從父到子傳遞。QGraphicsItem也可以為它的孩子處理所有的事件,這樣就允許以父親代表它所有的孩子,可以有效地把所有的items看作一個整體。 另外,QGraphicsItemGroup是一個特殊的item,它既對孩子事件進行處理又有一個介面把items從一個組中增加和刪除。把一個item加到 QGraphicsItemGroup仍會保留item的原始位置與變換,而給一個item重新指定父item則會讓item根據其新的父親重新定位。可以用QGraphicsScene::createItemGroup()建組。

 1 //myitem.cpp
 2 //myitem.h
 3 #ifndef MYITEM_H
 4 #define MYITEM_H
 5 #include <QGraphicsItem>
 6 class MyItem : public QGraphicsItem
 7 {
 8 public:
 9     MyItem();
10     QRectF boundingRect() const;
11     void paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget);
12 };
13 #endif // MYITEM_H
 1 //myitem.cpp
 2 #include "myitem.h"
 3 #include
 4 
 5 MyItem::MyItem()
 6 {
 7 
 8 }
 9 
10 QRectF MyItem::boundingRect() const
11 {
12     qreal adjust=0.5;
13     return QRectF(-18-adjust,-22-adjust,36+adjust,60+adjust);
14 }
15 
16 void MyItem::paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget)
17 {
18     painter->drawRect(0,0,200,200);
19 }
 1 //main.cpp
 2 2
 3 #include <QtGui/QApplication>
 4 #include 
 5 #include 
 6 #include 
 7 #include 
 8 #include"myitem.h"
 9 
10 int main(int argc, char *argv[])
11 {
12     QApplication a(argc, argv);
13     QGraphicsScene scene;
14     scene.setSceneRect(-300,-300,600,600);
15     scene.setItemIndexMethod(QGraphicsScene::NoIndex);
16     MyItem *item=new MyItem;
17     scene.addItem(item);
18     QGraphicsView view(&scene);
19     view.setRenderHint(QPainter::Antialiasing);
20     view.setCacheMode(QGraphicsView::CacheBackground);
21     view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
22     view.setDragMode(QGraphicsView::ScrollHandDrag);
23     view.resize(400,300);
24     view.show();
25     return a.exec();
26 }