Graphics View框架理論綜述
- 一、概述
- 二、Graphics View 體系結構
- 1. The Scene
- 2. The View
- 3. 圖元 Item
- 三、圖形視圖坐標系統
- 1. 圖元Item的坐標
- 2. Scene Scene坐標
- 3. View 視圖坐標
- 4. 坐標映射
- 四、關鍵特性
- 1. 縮放和旋轉
- 2. 打印
- 3. 拖放
- 4. 鼠標指針和 提示
- 5. 動畫
- 6. OpenGL渲染
- 7. Item group
- 8. 部件和布局
- 1. QGraphicsWidget
- 2. QGraphicsLayout
- 9. 對嵌入Widget的支持
- 五、性能
一、概述
Graphics View 提供了一個用于管理大量定制 2D 圖形項并與之交互的表面,以及一個用于可視化這些項的視圖部件,支持縮放和旋轉。
該框架包括一個事件傳播架構,允許Scene中的 Item 具有精確的雙精度交互能力。元素可以處理按鍵事件、鼠標按下、移動、釋放和雙擊事件,它們還可以跟蹤鼠標移動。
Graphics View 使用BSP(二叉空間劃分)樹來提供非常快速的 Item 發現,因此,它可以實時可視化大型Scene,即使有數百萬個 Item 。
Graphics View 在Qt 4.2中引入,取代了它的前身QCanvas。
二、Graphics View 體系結構
Graphics View 提供了一種基于項的模型視圖編程方法,很像InterView的便利類QTableView, QTreeView和QListView。多個視圖可以觀察同一個Scene,Scene中包含不同幾何形狀的物品。
1. The Scene
QGraphicsScene提供Graphics View的 Scene。Scene職責如下:
- 提供管理大量 Item 的快速界面
- 將事件傳播到每個 Item
- 管理 Item 狀態,例如選擇和焦點處理
- 提供未轉換的渲染功能;主要用于 Print 功能
Scene作為QGraphicsItem對象的容器。通過調用QGraphicsScene::addItem()將物品添加到Scene中,然后通過調用眾多物品發現函數中的一個來檢索物品。QGraphicsScene::items()及其重載函數返回由 點、矩形、多邊形或一般向量路徑包含或與之相交的所有元素。QGraphicsScene::itemAt() 返回指定位置的最頂層元素所有物品發現函數都以降序堆疊的方式返回物品(即,第一個返回的物品在最上面,最后一個返回的物品在最下面)。
QGraphicsScene scene;QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));QGraphicsItem *item = scene.itemAt(50, 50);// item == rect
QGraphicsScene的事件傳播架構調度Scene的事件以交付到 Item ,并管理 Item 之間的傳播。如果Scene在某個位置接收到鼠標按下事件,則Scene會將事件傳遞給位于該位置的任何元素。
QGraphicsScene還管理某些 Item 狀態,例如 Item 選擇和焦點。你可以通過調用QGraphicsScene::setSelectionArea()來選擇Scene中的元素,傳入一個任意形狀的參數。此功能也用作QGraphicsView中橡皮筋選擇的基礎。要取得當前所有選中項的列表,調用QGraphicsScene::selectedItems()。QGraphicsScene處理的另一個狀態是 Item 是否具有鍵盤輸入焦點。我們可以通過調用QGraphicsScene::setFocusItem()或QGraphicsItem::setFocus()來設置項上的焦點,或者通過調用QGraphicsScene::focusItem()來獲取當前的焦點項。
最后,QGraphicsScene允許我們通過QGraphicsScene::render()函數將Scene的部分渲染到繪圖設備中。
這就給我們提供了輸出成圖片提供了依據。
2. The View
QGraphicsView 提供了view控件,它可以可視化Scene的內容。你可以為同一個Scene添加多個視圖,從而為同一個數據集提供多個視口。view部件是一個滾動區域,提供了在大型Scene中導航的滾動條。要啟用OpenGL支持,可以調用QGraphicsView::setViewport()將QGLWidget設置為視圖。
QGraphicsScene scene;myPopulateScene(&scene);QGraphicsView view(&scene);view.show();
View 接收來自鍵盤和鼠標的輸入事件,并將這些事件轉換為Scene事件(將使用的坐標轉換為Scene坐標),然后將事件發送到可視化Scene。
使用它的轉換矩陣 QGraphicsView::transform(),視圖可以轉換Scene的坐標系統。視圖還允許高級導航功能,如縮放和旋轉。為了方便,QGraphicsView 還提供了在視圖和Scene坐標之間進行轉換的函數:QGraphicsView::mapToScene() 和 QGraphicsView::mapFromScene()。
3. 圖元 Item
QGraphicsItem 是Scene中圖形元素的基類。圖形視圖為常用的形狀提供了一些標準項,例如矩形(QGraphicsItem)、橢圓(QGraphicsEllipseItem) 和文本項 (QGraphicsTextItem),但在編寫自定義項時,我們可以使用最強大的 QGraphicsItem 功能。就是繼承這個功能。
QGraphicsItem 支持以下特性:
- 鼠標按下、移動、釋放和雙擊事件,以及鼠標懸停事件、滾輪事件和上下文菜單事件。
- 鍵盤輸入焦點和按鍵事件
- 拖放 Drop 和 Drag
- 分組管理,有父子關系,也有QGraphicsItemGroup 分組方式
- 碰撞檢測
Items 存在于一個局部坐標系中,像 QGraphicsView 一樣,它也提供了許多函數來映射物品和Scene之間的坐標,以及物品到物品之間的坐標。而且,像 QGraphicsView 一樣,它可以使用矩陣 QGraphicsItem::transform() 轉換其坐標系統。這對于旋轉和縮放單個元素非常有用。
Items 可以包含其他Item(子Item)。父項的變換會被其所有的子項繼承。然而,不管元素的累積變換如何,它的所有函數(例如QGraphicsItem::contains()、QGraphicsItem::boundingRect()、QGraphicsItem::collidesWith())仍然在局部坐標中操作。
QGraphicsItem通過QGraphicsItem::shape()函數和QGraphicsItem::collidesWith()函數支持碰撞檢測,這兩個函數都是虛函數。通過從QGraphicsItem::shape()返回 Item 的形狀作為局部坐標QPainterPath, QGraphicsItem將為你處理所有碰撞檢測。但是,如果你想提供自己的碰撞檢測,可以重新實現QGraphicsItem::collidesWith()。
三、圖形視圖坐標系統
圖形視圖基于笛卡兒坐標系: Item 在Scene中的位置和幾何形狀由兩組數字表示:x坐標和y坐標。當使用未變換的視圖觀察Scene時,Scene中的一個單位由屏幕上的一個像素表示。
注意:由于圖形視圖使用Qt的坐標系統,因此不支持反向y軸坐標系統(y向上增長)。
在圖形視圖中有三個有效的坐標系統:Item 坐標、Scene坐標和視圖坐標。為了簡化實現,Graphics View提供了方便的函數,允許你在三個坐標系統之間進行映射。
渲染時,圖形視圖的Scene坐標對應于QPainter的邏輯坐標,視圖坐標與設備坐標相同。在坐標系統文檔中,我們可以閱讀有關邏輯坐標和設備坐標之間的關系。
1. 圖元Item的坐標
元素存在于自己的局部坐標系統中。它們的坐標通常以中心點(0,0)為中心,這也是所有變換的中心。物品坐標系中的幾何基元通常被稱為物品點、物品線或物品矩形。
創建自定義item時,item的坐標是你需要擔心的;QGraphicsScene和QGraphicsView將為我們執行所有轉換。這使得實現自定義項非常容易。例如,如果你收到了一個鼠標按下或拖動進入事件,事件的位置會在元素的坐標中給出。
QGraphicsItem::contains()虛函數,如果某個點在 Item 內部,則返回true,否則返回false,它接受 Item 坐標中的point參數。類似地, Item 的邊界矩形和形狀是在 Item 的坐標中。
At item的位置是item在其父坐標系中的中心點的坐標;有時也被稱為父坐標。在這個意義上,Scene被視為所有無父項的“父”。頂級物品的位置在Scene坐標中。
子元素坐標是相對于父元素坐標的。如果子元素未進行轉換,則子元素坐標與父元素坐標之間的差值與父元素坐標中 Item 之間的距離相同。例如:如果一個未變換的子項精確地定位在父項的中心點上,那么兩個子項的坐標系統將是相同的。如果子節點的位置是(10,0),那么子節點的(0,10)點將對應于父節點的(10,10)點。
因為元素的位置和變換是相對于父元素的,所以子元素的坐標不受父元素變換的影響,盡管父元素的變換會隱式地變換子元素。在上面的例子中,即使父元素被旋轉和縮放,子元素的(0,10)點仍然對應于父元素的(10,10)點。然而,相對于Scene,子元素會跟隨父元素的變換和位置。如果父元素被縮放(2x, 2x),子元素的位置將在Scene坐標(20,0)處,而它的(10,0)點將對應于Scene中的(40,0)點。
QGraphicsItem::pos()是少數例外之一,QGraphicsItem的函數在物品坐標中操作,無論物品是什么,也不管它的父元素是什么變換。例如,一個 Item 的邊界矩形(即QGraphicsItem::boundingRect())總是以 Item 的坐標給出。
2. Scene Scene坐標
Scene代表所有 Item 的基礎坐標系統。Scene坐標系統描述了每個頂層 Item 的位置,同時也構成了從視圖發送到Scene的所有Scene事件的基礎。Scene中的每個 Item 都有一個Scene位置和邊界矩形(QGraphicsItem::scenePos(), QGraphicsItem::sceneBoundingRect()),除了它的局部 Item 位置和邊界矩形。Scene位置描述了 Item 在Scene坐標中的位置,它的Scene邊界矩形構成了QGraphicsScene如何確定Scene的哪些區域發生了變化的基礎。Scene的變化通過QGraphicsScene::changed()信號傳達,參數是Scene矩形的列表。
3. View 視圖坐標
視圖坐標是控件的坐標。視圖坐標中的每個單位對應一個像素。這個坐標系統的特別之處在于它是相對于widget(或視口)的,不受觀察到的Scene的影響。QGraphicsView的視口左上角始終是(0,0),右下角始終是(視口寬度,視口高度)。所有的鼠標事件和拖放事件最初都是以視圖坐標的形式接收的,你需要將這些坐標映射到Scene中才能與元素進行交互。
4. 坐標映射
通常在處理Scene中的物品時,將坐標和任意形狀從Scene映射到一個物品,從一個物品映射到另一個物品,或者從視圖映射到Scene,都是很有用的。例如,當你在QGraphicsView的視口中點擊鼠標時,你可以通過調用QGraphicsView::mapToScene()來詢問Scene光標下方是什么 Item ,接下來是QGraphicsScene::itemAt()。如果你想知道 Item 在視口中的位置,可以在 Item 上調用QGraphicsItem::mapToScene(),然后在視圖上調用QGraphicsView::mapFromScene()。最后,如果你想找到視圖橢圓內的 Item ,可以將QPainterPath傳遞給mapToScene(),然后將映射的路徑傳遞給QGraphicsScene::items()。
通過調用QGraphicsItem::mapToScene()和QGraphicsItem::mapFromScene(),可以將坐標和形狀映射到物品的Scene中。你也可以通過調用QGraphicsItem::mapToParent()和QGraphicsItem::mapFromParent()映射到一個 Item 的父 Item ,或者通過調用QGraphicsItem::mapToItem()和QGraphicsItem::mapFromItem()在 Item 之間映射。所有的映射函數都可以同時映射點、矩形、多邊形和路徑。
在視圖中也有相同的映射函數,用于與Scene的映射。QGraphicsView::mapFromScene()和QGraphicsView::mapToScene()。要從視圖映射到物品,首先要映射到Scene,然后再從Scene映射到物品。
四、關鍵特性
1. 縮放和旋轉
QGraphicsView通過QGraphicsView::setMatrix()支持與QPainter相同的仿射變換。通過對視圖應用轉換,我們可以輕松地添加對常見導航特性的支持,例如縮放和旋轉。
下面是一個如何在QGraphicsView的子類中實現縮放和旋轉槽的示例:
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); }...};
槽函數可以連接到啟用autoRepeat的QToolButtons。
當你轉換視圖時,QGraphicsView保持視圖中心對齊。
2. 打印
圖形視圖通過其渲染函數QGraphicsScene::render()和QGraphicsView::render()提供單行打印。這些函數提供了相同的API:你可以通過將QPainter傳遞給任意一個渲染函數,讓Scene或視圖將它們的全部或部分內容渲染到任何繪制設備中。這個例子展示了如何使用QPrinter將整個Scene打印到一個完整的頁面中。
QGraphicsScene scene;scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt::black), QBrush(Qt::green));QPrinter printer;if (QPrintDialog(&printer).exec() == QDialog::Accepted) {QPainter painter(&printer);painter.setRenderHint(QPainter::Antialiasing);scene.render(&painter);}
Scene和視圖渲染函數之間的區別是一個在Scene坐標中操作,另一個在視圖坐標中操作。QGraphicsScene::render()通常用于未變換地打印Scene的整個片段,例如繪制幾何數據或打印文本文檔。另一方面,QGraphicsView::render()適合截屏;它的默認行為是使用提供的painter渲染這個控件窗口外面的內容。
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");
當源區域和目標區域大小不匹配時,源區域內容被拉伸以適應目標區域。通過向你正在使用的渲染函數傳遞Qt::AspectRatioMode,你可以選擇在內容拉伸時保持或忽略Scene的縱橫比。
3. 拖放
因為QGraphicsView間接地繼承了QWidget,它已經提供了QWidget提供的相同的拖放功能。此外,為了方便,圖形視圖框架提供了對Scene和每個 Item 的拖放支持。當視圖接收到拖動時,它將拖放事件轉換為QGraphicsSceneDragDropEvent,然后將其轉發到Scene。Scene接管了這個事件的調度,并將其發送給鼠標光標下接受drop的第一個 Item 。
要從一個元素開始拖動,創建一個QDrag對象,向開始拖動的部件傳遞一個指針。多個視圖可以同時觀察 Item ,但只有一個視圖可以啟動拖動。在大多數情況下,拖動是從按下或移動鼠標開始的,因此在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();}
為了攔截Scene的拖放事件,我們需要在QGraphicsItem子類中重新實現QGraphicsScene::dragEnterEvent()和特定Scene需要的任何事件處理程序。我們可以在每個QGraphicsScene的事件處理程序的文檔中閱讀有關拖放圖形視圖的更多信息。
Item 可以通過調用QGraphicsItem::setAcceptDrops()來啟用拖放支持。要處理傳入的拖動,請重新實現QGraphicsItem::dragEnterEvent()、QGraphicsItem::dragMoveEvent()、QGraphicsItem::dragLeaveEvent()和QGraphicsItem::dropEvent()。
4. 鼠標指針和 提示
與QWidget類似,QGraphicsItem也支持鼠標(QGraphicsItem::setCursor())和ToolTip (QGraphicsItem::setToolTip())。當鼠標光標進入物品區域(通過調用QGraphicsItem::contains()檢測到)時,QGraphicsView會激活鼠標指針和ToolTip。
你也可以通過調用QGraphicsView::setCursor()直接在視圖上設置默認鼠標指針。
5. 動畫
圖形視圖支持多個級別的動畫。你可以通過使用動畫框架輕松地組裝動畫。為此,我們需要我們的 Item 繼承QGraphicsObject并將QPropertyAnimation與它們關聯。QPropertyAnimation允許對任何QObject屬性進行動畫。
另一種選擇是創建一個自定義項,它繼承自QObject和QGraphicsItem。該 Item 可以設置自己的定時器,并在QObject::timerEvent()中以增量步驟控制動畫。
第三種選擇是通過調用QGraphicsScene::advance()來推進Scene,它又會調用QGraphicsItem::advance(),這主要是為了在Qt 3中與QCanvas兼容。
6. OpenGL渲染
要啟用OpenGL渲染,只需通過調用QGraphicsView::setViewport()設置一個新的QGLWidget作為QGraphicsView的視口。如果你想要OpenGL抗鋸齒,你需要OpenGL樣本緩沖支持(參見QGLFormat::sampleBuffers())。
QGraphicsView view(&scene);view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
7. Item group
通過將一項作為另一項的子元素,可以實現項分組最基本的特性:所有項都在一起移動,所有變換都可以從父元素傳播到子元素。
此外,QGraphicsItemGroup是一個特殊項,它將子事件處理與用于向組中添加和從組中刪除項的有用接口結合在一起。將一個元素添加到QGraphicsItemGroup將保持元素的原始位置和變換,而重新排列元素通常會導致子元素相對于新的父元素重新定位。為了方便,你可以通過Scene調用QGraphicsScene::createItemGroup()來創建QGraphicsItemGroups。
8. 部件和布局
Qt 4.4通過qgraphicwidget引入了對幾何和布局感知 Item 的支持。這個特殊的基礎項類似于QWidget,但與QWidget不同的是,它不是繼承自QPaintDevice;而不是來自QGraphicsItem。這允許我們編寫具有事件、信號和槽函數、大小提示和策略的完整窗口組件,我們還可以通過QGraphicsLinearLayout和QGraphicsGridLayout在布局中管理窗口組件的幾何形狀。
1. QGraphicsWidget
基于QGraphicsItem的功能和精簡功能,QGraphicsWidget提供了兩者的精華:來自QWidget的額外功能,如樣式、字體、調色板、布局方向及其幾何形狀,以及來自QGraphicsItem的分辨率獨立性和轉換支持。因為圖形視圖使用真實坐標而不是整數,所以QGraphicsWidget的幾何函數也可以操作QRectF和QPointF。這也適用于frame rects、margin和spacing。例如,使用QGraphicsWidget時,將內容邊距指定為(0.5,0.5,0.5,0.5)是很常見的。既可以創建子窗口組件,也可以創建“頂級”窗口;在某些情況下,我們現在可以為高級MDI應用程序使用圖形視圖。
支持QWidget的一些屬性,包括窗口標志和屬性,但不是全部。我們應該參考qgraphicwidget的類文檔,以獲得支持什么和不支持什么的完整概述。例如,你可以通過傳遞Qt::Window窗口標志給qgraphicwidget的構造函數來創建裝飾窗口,但是圖形視圖目前不支持macOS上常見的Qt::Sheet和Qt::Drawer標志。
2. QGraphicsLayout
QGraphicsLayout是專門為qgraphicwidget設計的第二代布局框架的一部分。它的API與QLayout非常相似。你可以在QGraphicsLinearLayout和QGraphicsGridLayout中管理部件和子布局。你也可以通過自己繼承QGraphicsLaytItem來輕松編寫自己的布局,或者通過編寫QGraphicsLayoutItem的適配器子類來將自己的QGraphicsItem Item 添加到布局中。
9. 對嵌入Widget的支持
圖形視圖為將任何控件嵌入Scene提供無縫支持。可以嵌入簡單的控件(如QLineEdit或QPushButton),復雜的控件(如QTabWidget),甚至是完整的主窗口。要將控件嵌入到Scene中,只需調用QGraphicsScene::addWidget(),或創建一個QGraphicsProxyWidget 實例來手動嵌入控件。
通過QGraphicsProxyWidget, Graphics View能夠深度集成客戶端widget功能,包括其光標,ToolTip,鼠標,平板電腦和鍵盤事件,子widget,動畫,彈出窗口(例如,QComboBox或QCompleter),以及widget的輸入焦點和激活。QGraphicsProxyWidget甚至集成了嵌入式widget的選項卡順序,以便我們可以用選項卡進出嵌入式widget。我們甚至可以將新的QGraphicsView嵌入到我們的Scene中,以提供復雜的嵌套Scene。
在轉換嵌入式控件時,Graphics View確保控件獨立轉換分辨率,允許字體和樣式在放大時保持清晰。(注意,分辨率獨立性的效果取決于樣式。)
五、性能
為了準確、快速地為元素應用變換和特效,圖形視圖的構建假設用戶的硬件能夠為浮點指令提供合理的性能。
許多工作站和桌面計算機都配備了適當的硬件來加速這種計算,但一些嵌入式設備可能只提供處理數學運算或在軟件中模擬浮點指令的庫。
因此,在某些設備上,某些類型的效果可能比預期的要慢。可以通過在其他方面進行優化來彌補這種性能損失;例如,通過使用OpenGL渲染一個Scene。但是,如果這種優化本身也依賴于浮點硬件,那么性能就會下降。