QT開發(三十九)——GraphicsView架構
本文主要翻譯自QT 5.6.2GraphicsView官方文檔
一、GraphicsView架構簡介
QT4.2開始引入了Graphics View架構用來取代QT3中的Canvas子產品,并作出了改進,Graphics View架構實作了模型-視圖結構的圖形管理,能對大量圖元進行管理,支援碰撞檢測,坐标變換和圖元組等多種友善的功能。
GraphicsView架構結構主要包含三個主要的類QGraphicsScene(場景)、QGraphicsView(視圖)、QGraphicsItem(圖元)。QGraphicsScene本身不可見,是一個存儲圖元的容器,必須通過與之相連的QGraphicsView視圖來顯示及與外界進行互動,主要提供圖元的操作接口、傳遞事件和管理各個圖元狀态,提供無變換的繪制功能(如列印);QGraphicsView提供一個可視的視窗,用于顯示場景中的圖元,一個場景中可以有多個視圖。QGraphicsItem是場景中各個圖元的基礎類,QT提供了常用圖形圖元的标準類,如矩形(QGraphicsRectItem)、橢(QGraphicsEllipseItem)、文本(QGraphicsTextItem)。
GraphicsView是一個基于圖元的Model/View架構的架構,每一個元件都是一個獨立的元素。QPainter采用面向過程的描述方式繪圖;GraphicsView采用面向對象的描述方式繪圖。GraphicsView繪圖時首先建立一個場景,然後建立圖元對象(如一個直線對象、一個多邊形對象),再使用場景的add()函數,将圖元對象添加到場景中,最後通過視圖進行顯示。對于複雜的圖像來說,如果圖像包含大量的直線、曲線、多邊形等圖元對象,管理圖元對象比管理QPainter的繪制過程語句要容易,并且圖元對象更符合面向對象的思想,圖形的可複用性更好。
二、QGraphicsScene場景
QGraphicsScene場景是QGraphicsItem對象的容器,主要功能如下:
A、提供管理大量圖元的快速接口
B、傳播滑鼠、鍵盤等事件給場景中的每個圖元
C、管理圖元狀态,如圖元選擇和焦點處理
D、提供無變換的渲染功能,如列印
通過函數QGraphicsScene::addItem()可以加入一個圖元到場景中。圖元可以通過多個函數進行檢索:QGraphicsScene::items()及重載函數可以傳回和點、矩形、多邊形或向量路徑相交的所有圖元。QGraphicsScene::itemAt()傳回指定點的最頂層圖元。所有圖元查找函數按照遞減棧順序傳回圖元,第一個傳回的圖元位置最頂層,最後一個傳回的圖元位于最底層。
QGraphicsScene的事件傳播體系将場景事件發送給圖元,同時也管理圖元之間的事件傳播。如果場景收到了在某一點的滑鼠單擊事件,場景會把事件傳給在這一點的最頂層圖元。QGraphicsScene負責管理一些圖元的狀态,如圖元選擇和焦點。通過QGraphicsScene::setSeletionArea()函數選擇多個圖元,選擇區域可以是任意的形狀,使用 QPainterPath表示;要得到目前選擇的圖元清單可以使用 QGraphicsScene::selectedItems()函數;QGraphicsScene還管理圖元的鍵盤輸入焦點狀态,可以通過QGraphicsScene::setFocusItem()函數或者QGraphicsItem::setFoucs()函數來設定圖元的焦點;獲得目前具有焦點的圖元使用函數QGraphicsScene::foucsItem()。可以使用 QGraphicsScene::render()函數在繪圖裝置上繪制場景。
三、QGraphicsView視圖
QGraphicsView是視圖視窗部件,使場景内容可視化,可以連接配接多個視圖到一個場景,也可以為相同資料源的資料集提供不同的視圖。QGraphicsView是可滾動的視窗部件,可以提供滾動條來浏覽大的場景。如果需要使用OpenGL,可以使用QGraphicsView::setViewport()将視圖設定為QGLWidget元件。
視圖接收鍵盤和滑鼠的輸入事件,并把事件翻譯為場景事件(将坐标轉換為場景的坐标),再發送到場景。使用變換矩陣函數QGraphicsView::martix()可以變換場景的坐标系統,通過變換場景的坐标系統可以實作場景的縮放和旋轉。為了友善,QGraphicsView提供了視圖和場景的坐标轉換函數:QGraphicsView::mapToScene()和QGraphicsView::mapFromScene()。
四、QGraphicsItem圖元
QGraphicsItem是圖元的基類。QGraphics View架構提供了多種标準的圖元:
QGraphicsEllipseItem 橢圓圖元
QGraphicsLineItem 直線圖元
QGraphicsPathItem 路徑圖元
QGraphicsPixmapItem 圖像圖元
QGraphicsPolygonItem 多邊形圖元
QGraphicsRectItem 矩形圖元
QGraphicsSimpleTextItem 簡單文本圖元
QGraphicsTextItem 文本浏覽圖元
使用者可以繼承QGraphicsItem實作自定義的圖元。
QGraphicsItem圖元主要特性如下:
A、支援滑鼠按下、移動、釋放、輕按兩下、懸停、滾動和右鍵菜單事件。
B、支援鍵盤輸入焦點和按鍵事件
C、支援拖拽事件
D、支援分組,使用父子關系和QGraphicsItemGroup
E、支援碰撞檢測
圖元存在于本地坐标系統上,場景提供了在圖元和場景間、圖元與圖元間進行坐标變換的函數。QGraphicsItem::transform()函數可以使用矩陣轉換坐标系統。這對于翻轉和縮放圖元是有用的。
圖元可以包含其他圖元,父圖元的變換會被其所有的子圖元繼承。無論一個圖元本身有多少變換,圖元的所有函數(QGraphicsItem::contains(), QGraphicsItem::boundingRect(), QGraphicsItem::collidesWith())仍舊執行在本地坐标系上。
QGraphicsItem通過虛函數shape()和collideWith())來支援碰撞檢測。從shape()傳回圖元的形狀(以本地坐标QPainterPath表示),QGraphicsItem會處理所有的碰撞檢測。如果要提供自己的碰撞檢測,需要重新實作QGraphicsItem::collideWith()。
碰撞檢測的方法:
a、重寫shape()函數來傳回圖元的精準輪廓,依靠預設的collidesWithItem()來做外形交集。如果item輪廓和複雜時候,消耗是很大的。
b、重寫collidesWithItem(),提供一個自己的圖元和輪廓碰撞的算法
Contains()函數可以調用,用來決定一個圖元是否包含一個點。Contains函數可以重寫,contains()函數預設的方法是通過調用shape()來完成的。
圖元中也可以包含其他的圖元,也可以被别的圖元包含,所有的圖元可以有一個父類圖元和多個子類圖元,除非一個圖元沒有父類,否則圖元的位置是在父類坐标中,子類圖元将會繼承父類圖元的位置和轉換。
通過調用setVisible(),可以設定圖元是否可見,隐藏一個圖元同時也隐藏了其子類,通過調用 setEnabled()來是指圖元是否可用。如果禁用了圖元,那麼其所有的子類都不可用。圖元預設都是可見和可用的。
五、GraphicsView坐标系統
Graphics View坐标系基于笛卡爾坐标系,圖元的場景中的位置和幾何形狀通過x坐标和y坐标表示。當使用沒有變換的視圖觀察場景時,場景中的一個機關對應螢幕上的一個像素。
Graphics View架構中有三個有效的坐标系統,圖元坐标、場景坐标和視圖坐标。Graphics View提供了三個坐标系統之間的轉換函數。在繪制圖形時,QGraphics View的場景坐标對應QPainter的邏輯坐标,QGraphics View的視圖坐标對應QPainter的裝置坐标。
1、圖元坐标
圖元存在于自己的本地坐标上,圖元的坐标系統通常以圖元中心為原點,圖元中心也是所有坐标變換的原點,圖元坐标方向是x軸正方向向右,y軸正方向向下。建立自定義圖元時,隻需要注意圖元的坐标,QGraphicsScene和QGraphicsView會完成所有的變換。 例如,如果接受到一個滑鼠按下或拖入事件,所給的事件位置是基于圖元坐标系的。如果某個點位于圖元内部,使用圖元上的點作為QGraphicsItem::contains()虛函數的參數,函數會傳回true。類似,圖元的邊界矩形和形狀也是基于圖元坐标系。
圖元的位置是圖元的中心點在其父圖元坐标系統的坐标。按這種說法,場景是所有無父圖元的圖元的父圖元。頂層圖元的位置是場景坐标。
子圖元的坐标與父圖元的坐标相關。如果子圖元無變換,子圖元坐标和父圖元坐标之間的差別與他們的父圖元的坐标相同。例如,如果一個無變換的子圖元精确的位于父圖元的中心點,父子圖元的坐标系統是相同的。如果子圖元的位置是(10,0),子圖元上的點(0,10)就是父圖元上的點(10,10)。
由于圖元的位置和變換與父圖元相關,但子圖元的坐标并不會被父圖元的變換影響,雖然父圖元的變換會隐式地變換子圖元。在上例中,即使父圖元被翻轉和縮放,子圖元上的點(0,10)仍舊是父圖元上的點(10,10)。
如果調用QGraphicsItem類的paint()函數重繪圖元時,則以圖元坐标系為基準。
2、場景坐标
場景坐标是所有圖元的基礎坐标系統。場景坐标系統描述了頂層圖元的位置,并且構成從視圖傳播到場景的所有場景事件的基礎。每個圖元在場景上都有場景坐标和邊界矩形。場景坐标的原點在場景中心,坐标原點是X軸正方向向右,Y軸正方向向下。
3、視圖坐标
視圖坐标是視窗部件的坐标,視圖坐标的機關是像素,QGraphicsView的左上角是(0,0)。所有滑鼠事件、拖拽事件最開始都使用視圖坐标,為了和圖元互動,需要轉換坐标為場景坐标。
4、坐标變換
在Graphics View架構中,經常需要将多種坐标變換,從場景到圖元,從圖元到圖元,從視圖到場景 。QGraphics View架構坐标變換函數如下:
QGraphicsView::mapToScene()視圖到場景
QGraphicsView::mapFromScene() 場景到視圖
QGraphicsItem::mapFromScene() 場景到圖元
QGraphicsItem::mapToScene() 圖元到場景
QGraphicsItem::mapToParent() 子圖元到父圖元
QGraphicsItem::mapFromParent() 父圖元到子圖元
QGraphicsItem::mapToItem()本圖元到其他圖元
QGraphicsItem::mapFromItem()其他圖元到本圖元
在場景中處理圖元時,從場景到圖元、從圖元到圖元、從視圖到場景進行坐标和圖形變換是有用的。當在QGraphicsView的視口中點選滑鼠時,應該通過調用QGraphicsView::mapToScence()與QGraphicsScene::itemAt()來獲知光标下是場景中的哪個圖元;如果想獲知一個圖元位于視口中的位置,應該先在圖元上調用QGraphicsItem::mapToScene(),然後調用QGraphicsView::mapFromScene();如果想獲知在一個視圖橢圓中有哪些圖元,應該把QPainterPath傳遞到mapToScene(),然後再把映射後的路徑傳遞到QGraphicsScene::items()。 可以調用QGraphicsItem::mapToScene()與QGraphicsItem::mapFromScene()在圖元與場景之間進行坐标與形狀的映射,也可以在子圖元與其父圖元之間通過QGraphicsItem::mapToParent()與QGraphicsItem::mapFromItem()進行映射。所有映射函數可以包括點、矩形、多邊形、路徑。視圖與場景之間的映射也與此類似。對于視圖與圖元之間的映射,應該先從視圖映射到場景,然後再從場景圖映射到圖元。
六、GraphicsView架構特性
1、縮放與旋轉
QGraphicsView通過QGraphicsView::setMatrix()支援同QPainter一樣的坐标變換,通過對一個視圖應用變換,可以很容易地支援普通的導航特性如縮放與旋轉。
2、列印
圖形視圖架構通過渲染函數QGraphicsScene::render()和QGraphicsView::render()支援單行列印
場景和視圖的渲染函數的不同在于QGraphicsScene::render()使用場景坐标,QGraphicsView::render()使用視圖坐标。QGraphicsScene::render()經常用于列印未變換場景中的整塊,例如一塊圖形資料或是列印一個文本文檔。 QGraphicsView::render()适合用于截屏,預設會使用繪圖裝置精确渲染視口的内容。
QGraphicsScene scene;
scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt::black), QBrush(Qt::green));
QPixmap pixmap;
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
scene.render(&painter);
painter.end();
pixmap.save("scene.png");
當源和目标區尺寸不比對時,源的内容會比例縮放适合目标區。
3、拖拽
由于QGraphicsView繼承自QWidget,GraphicsView同樣提供了拖拽功能。此外,為了友善,GraphicsView架構也為場景、圖元提供拖拽支援。當視圖接收到拖拽事件,GraphicsView架構會将拖拽事件翻譯為QGraphicsSceneDragDropEvent事件,再發送到場景,場景接管事件,再把事件發送到光标下接受拖拽的第一個圖元。
為了開啟圖元拖拽,建立一個QDrag對象,傳遞啟動拖拽的QWidget的指針。圖元可以同時被多個視圖觀察,但隻有一個視圖可以拖拽圖元。通常,拖拽是從按下滑鼠或是移動滑鼠開始的,在mousePressEvent()或mouseMoveEvent()中,可以從事件中得到原始的QWidget指針。
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子類裡特定場景需要的事件處理器。
圖元也可以通過調用QGraphicsItem::setAcceptDrops()獲得拖拽支援,為了處理将要進行的拖拽,需要重新實作QGraphicsItem的dragEnterEvent()、dragMoveEvent()、dropEvent()、dragLeaveEvent() 。
4、光标與工具提示
QGraphicsItem支援光标(QgraphicsItem::setCursor)與工具提示(QGraphicsItem::setToolTip())。當光标進入到圖元的區域,光标與工具提示被QGraphicsView激活(通過調用QGraphicsItem::contains()檢測),也可以直接在視圖上設定一個預設光标(QGraphicsView::setCursor)。
5、動畫
GraphicsView架構支援多種層次的動畫。使用動畫架構可以很容易制作出動畫。
GraphicsView架構支援的動畫實作種類如下:
A、圖元需要繼承自QGraphicsObject,并且需要聯結QPropertyAnimation屬性。
B、建立繼承自QObject和QGraphicsItem的圖元,圖元可以設定自己的定時器,在QObject::timeEvent()中增加步進的方式來控制動畫。
C、通過調用QGraphicsScene::advance()來推進場景,依次調用QGraphicsItem::advance()。
6、OpenGL渲染
為了使用OpenGL渲染,需要設定一個新的QGLWidget作為QGraphicsView的視口:QGraphicsView::setViewPort()。如果需要OpenGL提供反鋸齒功能,則需要OpenGL采樣緩沖支援。
QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
7、圖元組
通過把一個圖元做為另一個圖元的孩子,可以得到圖元組的大多數本質特性:所有圖元會一起移動,所有變換會從父到子傳遞。
另外,QGraphicsItemGroup是一個特殊的圖元。為了增加和删除圖元,它使用一個有用接口合并了子圖元的事件處理。把一個圖元加到QGraphicsItemGroup仍會保留圖元的原始位置與變換,而給一個圖元重新指定父圖元則會讓圖元根據其新的父親重新定位。可以用QGraphicsScene::createItemGroup()建立圖元組。
8、圖形元件和布局
QT4.4通過QGraphicsWidget支援圖形和圖元布局。QGraphicsWidget類似于QWidget,但QGraphicsWidget并不從QPaintDevice繼承,而是繼承自QGraphicsItem。QGraphicsWidget支援事件、信号與槽、大小和政策。通過QGraphicsLinearLayout、QGraphicsGridLayout可以對圖形元件進行布局管理。
QGraphicsWidget繼承了QWidget和QGraphicsItem的優點,如QWidget的樣式、字型、調色闆、布局方向以及QGraphicsItem的圖形、獨立精度和變換支援。
QGraphicsLayout是專為QGraphicsWidget特殊設計的第二代布局架構。QGraphicsLayout的API類似于QLayout。通過QGraphicsLinearLayout和QGraphicsGridLayout可以管理元件與子布局。
9、嵌入元件
圖形視圖架構為嵌入任何元件到場景提供了無縫支援。可以嵌入簡單的元件,如QLineEdit、QPushButton,或是複雜的元件如QTableWidget,甚至是主視窗。