PyQt5系列教程(84):一個簡單GraphicsView例子(GraphicsView框架介紹)
本期我們先來介紹下Graphics View框架。
Graphics View提供了一個平面,用於管理和交互大量自定義的2D圖形圖元,以及一個用於可視化圖元的視圖窗口小部件,支持縮放和旋轉。
該框架包括一個事件傳播架構,允許場景中圖元的精確雙精度交互功能。圖元可以處理關鍵事件,滑鼠按下,移動,釋放和雙擊事件,還可以跟蹤滑鼠移動。
Graphics View使用BSP(二進位空間分區)樹來提供非常快速的圖元發現,因此,即使有數百萬個圖元,它也可以實時顯示大型場景。
圖形視圖架構
Graphics View提供了一種基於圖元的模型-視圖編程方法,就像QTableView,QTreeView和QListView一樣。多個視圖可以觀察單個場景,並且場景包含具有不同幾何形狀的圖元。
如同下面的綠巨人,視圖:強壯的顏值,模型:所有的分析來源於其強大的頭腦,人家也好歹也是著名物理學家啊!

在正式介紹場景、視圖、圖元的概念前,我們嘗試通過燒螃蟹的過程,舉例說明這幾個概念,如下圖:
在上圖中,我們假設,鍋就是場景、螃蟹就是圖元、廚房就是視圖。燒螃蟹的幾個步驟如下:
1、往鍋中放入4隻螃蟹(向場景中加入圖元)
2、場景受外部影響的同時也影響到了圖元(給鍋加熱,螃蟹燒紅了)3、這一切都發生在廚房之中(場景在視圖中)在了解到燒螃蟹這幾個步驟後我們再來說明以下幾個概念。
場景(The Scene)
QGraphicsScene提供圖形視圖場景。該場景具有以下職責:
- 提供用於管理大量圖元的快速界面(鍋)
- 將事件傳播到每個圖元(把螃蟹燒熟了)
- 管理圖元狀態,例如選擇和焦點處理
- 提供未轉換的渲染功能;主要用於列印
該場景充當QGraphicsItem對象的容器(鍋)。通過調用QGraphicsScene.addItem()將圖元(螃蟹)添加到場景中,然後通過調用許多圖元發現函數的一個來檢索圖元。 QGraphicsScene.items()及其重載返回由點、矩形、多邊形或一般矢量路徑包含或相交的所有圖元。 QGraphicsScene.itemAt()返回特定點的最頂層項。所有圖元發現函數都按遞減堆疊順序返回圖元(即,第一個返回的圖元位於最頂層,最後一個圖元位於最底層)。
QGraphicsScene的事件傳播體系結構調度場景事件以傳遞到圖元,還管理圖元之間的傳播(把螃蟹燒熟的過程)。如果場景在某個位置接收到滑鼠按下事件,則場景將事件傳遞給該位置處的任何圖元。
QGraphicsScene還管理某些圖元狀態,例如圖元選擇和焦點。您可以通過調用QGraphicsScene.setSelectionArea()來選擇場景中的圖元,並傳遞任意形狀。此功能還可用作QGraphicsView中「橡皮筋」選擇框的基礎。要獲取所有當前所選圖元的列表,請調用QGraphicsScene.selectedItems()。 QGraphicsScene處理的另一個狀態是圖元是否具有鍵盤輸入焦點。您可以通過調用QGraphicsScene.setFocusItem()或QGraphicsItem.setFocus()來設置焦點,或通過調用QGraphicsScene.focusItem()獲取當前焦點的圖元。
最後,QGraphicsScene允許您通過QGraphicsScene.render()函數將場景的一部分渲染到繪圖設備中。
註:在圖形編輯應用中常會用到橡皮筋線,如選擇圖形的某個區域等,最常見的就是在系統桌面上用滑鼠拖動,可以繪製一個類似螞蟻線的選區,並且選區線能夠跟隨滑鼠的移動而伸縮,因此叫作橡皮筋線(橡皮筋框)。如下圖:
視圖(The View)
QGraphicsView提供了視圖窗口小部件(廚房),可以顯示場景的內容。 您可以將多個視圖附加到同一場景,以將多個視口提供到同一數據集中。 視圖小部件是一個滾動區域,並提供滾動條以瀏覽大型場景。 要啟用OpenGL支持,可以通過調用QGraphicsView.setViewport()將QGLWidget設置為視口。
視圖從鍵盤和滑鼠接收輸入事件,並在將事件發送到可視化場景之前將這些事件轉換為場景事件(在適當的情況下將使用的坐標轉換為場景坐標)。
使用其變換矩陣QGraphicsView.transform(),視圖可以變換場景的坐標系。 這允許高級導航功能,如縮放和旋轉。 為方便起見,QGraphicsView還提供了在視圖和場景坐標之間進行轉換的功能:QGraphicsView.mapToScene()和QGraphicsView.mapFromScene()。
圖元(The Item)
QGraphicsItem是場景中圖形項的基類。 Graphics View為典型形狀提供了幾個標準圖元,例如矩形(QGraphicsRectItem),橢圓(QGraphicsEllipseItem)和文本項(QGraphicsTextItem),但是當您編寫自定義圖元時,可以使用最強大的QGraphicsItem功能。除其他外,QGraphicsItem支持以下功能:
- 滑鼠按下,移動,釋放和雙擊事件,以及滑鼠懸停事件,滾輪事件和上下文菜單事件。
- 鍵盤輸入焦點和鍵事件
- 拖放
- 通過父子關係和QGraphicsItemGroup進行分組
- 碰撞檢測
圖元位於本地坐標系中,與QGraphicsView一樣,它還提供了許多功能,用於映射圖元和場景之間以及圖元之間的坐標。此外,與QGraphicsView一樣,它可以使用矩陣變換其坐標系:QGraphicsItem.transform()。這對於旋轉和縮放單個圖元很有用。
圖元可以包含其他圖元(子圖元)。父圖元的轉換由其所有子圖元繼承。然而,無論圖元的累積變換如何,其所有函數(例如,QGraphicsItem. contains(),QGraphicsItem.boundingRect(),QGraphicsItem.collidesWith())仍然在本地坐標中操作。
QGraphicsItem通過QGraphicsItem.shape()函數和QGraphicsItem.collidesWith()支持碰撞檢測,它們都是虛函數。通過將圖元的形狀作為QGraphicsItem.shape()的局部坐標QPainterPath返回,QGraphicsItem將為您處理所有碰撞檢測。但是,如果要提供自己的碰撞檢測,可以重新實現QGraphicsItem. collidesWith()。
Graphics View Framework中的類
這些類提供了創建互動式應用程序的框架:

一共35個類,每個類的具體作用請參考幫助文檔。
Graphics View的坐標體系
圖形視圖基於笛卡爾坐標系(平面直角坐標系x、y軸); 場景中的圖元位置和幾何圖形由兩個數字組成:x坐標和y坐標。 當使用未轉換的視圖觀察場景時,場景上的一個單元由屏幕上的一個像素表示。
注意:不支持反轉的Y軸坐標系(y向上增長),因為圖形視圖使用Qt的坐標系,也就是說x軸向右,y軸向下。如下圖:
圖形視圖中有三個有效的坐標系:圖元坐標,場景坐標和視圖坐標。 為了簡化您的實現,Graphics View提供了便利功能,允許您在三個坐標系之間進行映射。
渲染時,Graphics View的場景坐標對應於QPainter的邏輯坐標,視圖坐標與設備坐標相同。
圖元坐標(Item Coordinates)
圖元存在於他們自己的本地坐標系中。它們的坐標通常以其中心點(0,0)為中心,這也是所有變換的中心,如下圖:

圖元坐標系中的幾何圖元通常稱為圖元點,圖元線或圖元矩形。
創建自定義圖元時,您需要考慮圖元坐標; QGraphicsScene和QGraphicsView將為您執行所有轉換。這使得實現自定義圖元變得非常容易。例如,如果您收到滑鼠按下或拖動輸入事件,則事件位置以圖元坐標給出。 QGraphicsItem.contains()虛函數,如果某個點在您的圖元內,則返回True,否則返回False,在圖元坐標中獲取一個點參數。類似地,圖元的邊界矩形和形狀在圖元坐標中。
在圖元的位置是圖元中心點在其父坐標系中的坐標;有時也稱為父坐標。在這個意義上,場景被視為所有無父圖元的「父母」。頂級圖元的位置在場景坐標中。
子坐標是相對於父坐標的。如果子圖元未轉換,子坐標和父坐標之間的差異與父坐標中圖元之間的距離相同。例如:如果未轉換的子圖元精確定位在其父項的中心點,則兩個圖元的坐標系統將完全相同。但是,如果孩子的位置是(10,0),則孩子的(0,10)點將對應於其父坐標的(10,10)點。

由於圖元的位置和變換是相對於父項的,因此子項的坐標不受父項轉換的影響,儘管父項的轉換會隱式轉換子項。即使父項被旋轉和縮放,子項(0,10)點仍將對應於父項(10,10)點。然而,相對於場景,孩子將遵循父母的轉變和位置。如果縮放父級(2x,2x),則子級的位置將位於場景坐標(20,0),並且其(10,0)點將對應於場景上的點(40,0)。

由於QGraphicsItem.pos()是少數例外之一,QGraphicsItem的函數在項坐標中運行,無論圖元或其父項的任何轉換如何。例如,圖元的邊界矩形(即QGraphicsItem.boundingRect())總是在圖元坐標中給出。
場景坐標(Scene Coordinates)
場景表示其所有圖元的基本坐標系。場景坐標系描述每個頂級圖元的位置,並且還形成從視圖傳遞到場景的所有場景事件的基礎。除了本地圖元pos和邊界矩形之外,場景中的每個圖元都有一個圖元位置和邊界矩形(QGraphicsItem.scenePos(),QGraphicsItem. sceneBoundingRect())。場景位置描述了圖元在場景坐標中的位置,其場景邊界矩形構成了QGraphicsScene如何確定場景的哪些區域已經改變的基礎。場景中的變化通過QGraphicsScene.changed()信號傳遞,參數是場景矩形列表。
視圖坐標(View Coordinates)
視圖坐標是小部件的坐標。視圖坐標中的每個單元對應於一個像素。這個坐標系的特殊之處在於它相對於窗口小部件或視口,並且不受觀察場景的影響。 QGraphicsView視口的左上角始終為(0,0),右下角始終為(視口寬度,視口高度)。所有滑鼠事件和拖放事件最初都作為視圖坐標接收,您需要將這些坐標映射到場景以便與圖元進行交互。
坐標映射(Coordinate Mapping)
通常在處理場景中的圖元時,將場景中的坐標和任意形狀映射到圖元,圖元之間或視圖到場景都很有用。例如,當您在QGraphicsView的視口中單擊滑鼠時,可以通過調用QGraphicsView.mapToScene(),然後調用QGraphicsScene.itemAt()來詢問場景下游標下的圖元。如果您想知道圖元所在視口中的位置,可以在圖元上調用QGraphicsItem.mapToScene(),然後在視圖上調用QGraphicsView.mapFromScene()。最後,如果您使用想要查找視圖橢圓內的圖元,可以將QPainterPath傳遞給mapToScene(),然後將映射的路徑傳遞給QGraphicsScene.items()。
您可以通過調用QGraphicsItem.mapToScene()和QGraphicsItem.mapFromScene()來將坐標和形狀映射到圖元的場景中。您還可以通過調用QGraphicsItem.mapToParent()和QGraphicsItem.mapFromParent()或通過調用QGraphicsItem.mapToItem()和QGraphicsItem.mapFromItem()來調用圖元的父圖元。所有映射函數都可以映射點,矩形,多邊形和路徑。
視圖中提供了相同的映射函數,用於映射到場景和從場景映射。 QGraphicsView.mapFromScene()和QGraphicsView.mapToScene()。 要從視圖映射到圖元,首先映射到場景,然後從場景映射到圖元。
主要特點
縮放和旋轉
QGraphicsView支持與QPainter通過QGraphicsView.setMatrix()相同的仿射變換。 通過對視圖應用變換,您可以輕鬆添加對常用導航功能(如縮放和旋轉)的支持。
列印
Graphics View通過其渲染函數QGraphicsScene.render()和QGraphicsView.render()提供單行列印。這些函數提供相同的API:您可以通過將QPainter傳遞給任一渲染函數,讓場景或視圖將其內容的全部或部分渲染到任何繪圖設備中。
場景和視圖渲染功能之間的區別在於,一個在場景坐標中操作,另一個在視圖坐標中操作。QGraphicsScene.render()通常首選列印未轉換場景的整個片段,例如繪製幾何數據或列印文本文檔。另一方面,QGraphicsView.render()適用於截屏;它的默認行為是使用提供的畫家(painter)渲染視口的確切內容。
拖、放
因為QGraphicsView間接地繼承了QWidget,所以它已經提供了與QWidget提供的相同的拖放功能。 此外,為方便起見,Graphics View框架還為場景和每個圖元提供拖放支持。 當視圖收到拖動時,它會將拖放事件轉換為QGraphicsSceneDragDropEvent,然後將其轉發到場景中。 場景接管此事件的調度,並將其發送到接受丟棄的滑鼠游標下的第一個圖元。
要從圖元開始拖動,請創建QDrag對象,將指針傳遞給開始拖動的窗口小部件。 許多視圖可以同時觀察圖元,但只有一個視圖可以開始拖動。 在大多數情況下,拖動是由於按下或移動滑鼠而啟動的,因此在mousePressEvent()或mouseMoveEvent()中,您可以從事件中獲取原始窗口小部件指針。
要攔截場景的拖放事件,您需要在QGraphicsItem子類中重新實現QGraphicsScene.dragEnterEvent()以及您的特定場景所需的任何事件處理程序。您可以在QGraphicsScene的每個事件處理程序的文檔中閱讀有關拖放圖形視圖的更多信息。
圖元可以通過調用QGraphicsItem.setAcceptDrops()來啟用拖放支持。要處理傳入的拖動,請重新實現QGraphicsItem.dragEnterEvent(),QGraphicsItem.dragMoveEvent(),QGraphicsItem.dragLeaveEvent()和QGraphicsItem.dropEvent()。
游標和工具提示
與QWidget一樣,QGraphicsItem也支持游標(QGraphicsItem.setCursor())和工具提示(QGraphicsItem.setToolTip())。當滑鼠游標進入圖元區域時(通過調用QGraphicsItem.contains()檢測到),QGraphicsView將激活游標和工具提示。
您還可以通過調用QGraphicsView.setCursor()直接在視圖上設置默認游標。
最後
今天的內容有點枯燥,都是一些基礎內容,估計很多人都沒看明白。沒關係我第一次看得時候也不知道說的是什麼,後期我們還是會通過例子來學習。我們下期再見!
下午坐公交走神坐過了兩站,我對司機說我坐過站了,車內人全站了起來,又是遞煙又是說好話的,其中一個人掏出20元錢給我,說下一站讓我打計程車回去,我說:唉,這坐過站了回家飯都涼了!又有幾個人站起來掏出一百元給我說:你別激動,這些錢給你,等下路過飯店叫倆小菜回去喝點,我都不知道世界怎麼突然變美了??
社會充滿正能量啊!!
如果你喜歡本篇文章,請給我點贊

讚賞(推薦)
分享給你的好友們吧!
歡迎關注微信公眾號:學點編程吧。加油!
(? ??_??)? (*????)
