The Graphic View 提供了一個 Surface,用于管理和交互大量定制的 2D 圖形化 Item;同時還提供了一個用于可視化這些 Items 的 View 組件,這個 View 支持縮放和旋轉(zhuǎn)。
該框架包括事件傳播架構(gòu),可以為場景中的 Item 提供精確的雙精度交互能力。 Item 可以處理鍵盤事件,鼠標的按壓、釋放、移動和雙擊事件,也可以跟蹤鼠標移動。
Graphics View 使用 BSP(二叉搜索樹)提供非??焖俚?Item 查找,因此,它可以實時顯示大型場景,即使包含了數(shù)百萬個 Item。
Graphics View 在 Qt 4.2 中引入,取代了其前身 QCanvas。
Graphic View 的架構(gòu)
Graphics View 提供了一種基于 Item 的 Model-View 編程方法,和 InterView 中的便捷類 QTableView,QTreeView QListView 一樣,多個 View 可以觀察單個 Scene,Scene 中包含不同幾何形狀的 Item。
The Scene
QGraphicsScene 提供了 Graphic View 的場景,場景有以下職責:
- 提供一個高性能的接口來管理大量的 Items
- 將事件傳播到每個 Item
- 管理 Item 狀態(tài),如選擇和焦點處理
- 提供未被變換的渲染能力,主要用于打印
Scene 作為 QGraphicsItem 對象的容器,通過調(diào)用 QGraphicsScene::addItem() 將 Item 添加到 Scene 中。還有許多 Item 查找函數(shù),QGraphicsScene::items() 有多個重載版本,可以返回返回由點、矩形、多邊形或向量路徑包含或相交的所有 Items 。 QGraphicsScene::itemAt() 返回特定點的最上面的 Item 。 所有 Item 查找函數(shù)以降序堆疊順序返回找到的 Item(即,第一個返回的 Item 是最上面的,最后一個 Item 是最底部的)。
QGraphicsScene scene;
QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));
QGraphicsItem *item = scene.itemAt(50, 50);
// item == rect
QGraphicsScene 的事件傳播架構(gòu)調(diào)度 Scene 事件并傳遞給 Item,并且也管理 Item 之間的事件傳播。 如果 Scene 在特定位置接收到鼠標按壓事件,則 Scene 將傳遞事件到該位置的那個 Item。
QGraphicsScene 還管理某些 Item 狀態(tài):如 Item 的選擇和焦點。 您可以通過調(diào)用 QGraphicsScene::setSelectionArea() 傳遞一個任意形狀來選擇 Scene 中的 Item。 這個函數(shù)也是在 QGraphicsView 中進行橡皮筋選擇的基礎。 要獲取所有當前選中的 Item,可以調(diào)用 QGraphicsScene::selectedItems()。 QGraphicsScene 處理的另一個狀態(tài)是 Item 是否具有鍵盤輸入焦點,可以通過調(diào)用 QGraphicsScene::setFocusItem() 或 QGraphicsItem::setFocus() 來為 Item 設置焦點,或通過調(diào)用 QGraphicsScene::focusItem() 獲取當前的有焦點的 Item。
最后,QGraphicsScene 允許您通過 QGraphicsScene::render() 函數(shù)將 Scene 的一部分渲染到繪圖設備中。 您可以在本文檔后面的“打印”部分中閱讀更多信息。
The View
QGraphicsView 提供了 View 組件,用于可視化場景中的內(nèi)容。 您可以將多個 View 附加到同一個 Scene 上,相當于將同一個數(shù)據(jù)集提供給多個視口。View 組件也是一個滾動區(qū)域,并提供用于瀏覽大型場景的滾動條。 要啟用 OpenGL 支持,可以通過調(diào)用 QGraphicsView::setViewport() 將 QGLWidget 設置為視口。
QGraphicsScene scene;
myPopulateScene(&scene);
QGraphicsView view(&scene);
view.show();
視圖可以從鍵盤和鼠標接收事件,并將這些事件轉(zhuǎn)換為場景事件(同時將坐標轉(zhuǎn)換為場景坐標),然后將其發(fā)送給場景。
使用變換矩陣和 QGraphicsView::transform() ,視圖可以變換場景的坐標系。這樣可以實現(xiàn)一些高級導航功能:如縮放和旋轉(zhuǎn)。為方便起見,QGraphicsView 還提供了視圖和場景之間的坐標映射函數(shù):QGraphicsView::mapToScene() 和 QGraphicsView::mapFromScene() 。

The Item
QGraphicsItem 是場景中圖形化 Item 的基類。Graphic View 提供了幾個典型形狀的標準 Item,如矩形(QGraphicsRectItem),橢圓(QGraphicsEllipseItem)和文本項(QGraphicsTextItem)。當您編寫自定義 Item 時,QGraphicsItem 的強力特性都是可用的。除此之外,QGraphicsItem 還支持以下功能:
- 鼠標按下,移動,釋放和雙擊事件,以及鼠標懸停事件,滾輪事件和上下文菜單事件。
- 鍵盤輸入焦點和按鍵事件
- 拖放
- 通過父子關系和
QGraphicsItemGroup進行分組 - 碰撞檢測
每個 Item 都有自己的本地坐標系,像 QGraphicsView 一樣,它還提供許多函數(shù),用于在 Item 和 Scene 之間,以及在 Item 和 Item 之間映射坐標。此外,像 QGraphicsView 一樣,它也有使用矩陣來變換坐標系的函數(shù):QGraphicsItem::transform(),這對于旋轉(zhuǎn)和縮放各個獨立的 Item 很有用。
Item 可以包含其他子 Item。父 Item 的變換會被其所有子Item 繼承。除了變換會被累積, Item 的所有其他函數(shù)(例如,QGraphicsItem::contains(),QGraphicsItem::boundingRect(),QGraphicsItem :: collidesWith() 仍然在本地坐標中運行。
Item 可以通過 QGraphicsItem::shape() 函數(shù)進行碰撞檢測,這個函數(shù)和 QGraphicsItem::collidesWith() 都是虛函數(shù)。利用 QGraphicsItem::shape() 返回的 Item 形狀作為 QPainterPath 的局部坐標系,Item 可以處理所有的碰撞檢測。但是,如果要提供自己的碰撞檢測,可以重新實現(xiàn) QGraphicsItem::collidesWith() 。

The Graphics View 的坐標系
Graphics View 基于笛卡爾坐標系; Item 在場景中的位置和形狀由兩組數(shù)字表示: x 坐標和 y 坐標。 當使用沒有進行過坐標變換的視圖觀察場景時,場景上的一個單位就是屏幕上的一個像素。
注意:Graphics VIew 使用 Qt 的坐標系,不支持倒置的Y軸坐標系(即 y 向上增長)。
這樣,Graphics View 中就有三套的坐標系:Item 坐標、Scene 坐標和 View 坐標。 為了簡化您的實現(xiàn),Graphics View 提供了便于三個坐標系之間進行映射的函數(shù)。
渲染時,Scene 坐標對應于 QPainter 的邏輯坐標,View 坐標相當于設備坐標。 在《坐標系統(tǒng)》文檔中,您可以閱讀有關邏輯坐標和設備坐標之間的關系。

Item 坐標系
Item 有自己的本地坐標系,坐標原點為 (0, 0) ,這也是所有變換的原點。 Item 坐標系中的幾何圖元通常被稱為點、線或矩形。
當創(chuàng)建自定義 Item 時,唯一需要操心的就是 Item 坐標系; QGraphicsScene 和 QGraphicsView 會執(zhí)行所有變換,這使得實現(xiàn)自定義項目很容易。例如,如果您接收到鼠標點擊或拖動事件,事件位置將以 Item 坐標系給出。 QGraphicsItem::contains() 函數(shù)接收一個 Item 坐標系中的坐標參數(shù),如果這個坐標在 Item 上則返回 true,否則返回 false 。類似地,Item 的邊界和形狀也是在 Item 坐標系中描述的。
Item 的位置是 Item 原點在其父系坐標系中的坐標,稱為父坐標。Scene 被認為是所有無父元素的 Item 的 Parent,因此,頂層 Item 的位置在 Scene 坐標系中描述。
要將子元素的座標映射到父元素的坐標系中,需要經(jīng)過一番計算,例如:沒有變換的子元素的原點如果精確位于其父元素的原點上,則父子兩個 Item 的坐標系將相同;然而,如果子元素原點的位置在父坐標系中是(10,0) ,則子元素坐標系中的 (0,10) 在父坐標系中為 (10,10) 。
由于子元素的坐標系只參照父元素,因此子元素的坐標系不受父元素變換的影響。在上面的例子中,即使父元素被旋轉(zhuǎn)和縮放,子元素中的(0,10) 點映射到父元素坐標系中仍然是 (10,10) 點。然而,在 Scene 坐標系中,如果 Scene 坐標系發(fā)生了變換和重新定位。如 Scene 被縮放 (2x,2x) ,則子元素的 (0, 0) 位置在 Scene 坐標系中變?yōu)?(20,0) ,且其 (10,0) 點在 Scene 坐標系中變?yōu)?(40,0) 。Scene 坐標系可以看做是世界坐標系。
除了 QGraphicsItem::pos() 等少數(shù)例外,其他 QGraphicsItem 的函數(shù)都是在 Item 坐標系中操作,而不考慮 Item 自身或者其父元素的變換。例如,Item 的邊界矩形(即 QGraphicsItem::boundingRect())總是在 Item 坐標坐標系中。
Scene 坐標系
Scene 坐標系是 Item 的基準坐標系,每個頂層 Item 都是定位在 Scene 坐標系中,從 View 傳遞到 Scene 的所有事件也在 Scene 中定位。 Scene 中的每個 Item 除了在本地坐標系中有定位和邊界矩形之外,在場景坐標系中也具有場景位置 "scene position" 和邊界矩形(QGraphicsItem::scenePos() ,QGraphicsItem::sceneBoundingRect())。場景位置描述 Item 在場景坐標系中的位置,而場景邊界矩可以讓 Scene 確定場景的哪些區(qū)域已經(jīng)發(fā)生了改變。 Scene 中的變化通過 QGraphicsScene::changed() 信號傳遞,參數(shù)是場景矩形的列表。
View 坐標系
View 坐標系是組件的坐標系,View 坐標系中的每個單位對應于一個像素。 View 坐標系的全部控件就是組件窗口或者視口,它不受所觀察的 Scene 影響。GraphicsView 視口的左上角始終為 (0,0) ,右下角始終為 (視口寬度,視口高度) 。 所有的鼠標事件和拖放事件最初發(fā)生在 View 坐標系中,您需要將這些坐標映射到 Scene 中以便與 Item 進行交互。
坐標系映射
在處理場景中的 Item 時,常常需要將坐標和形狀從 Scene 映射到 Item、或者從一個 Item 映射到另一個 Item,或者從 View 映射到 Scene 。例如,當您在 QGraphicsView 的視口中單擊鼠標時,您可以通過調(diào)用 QGraphicsView::mapToScene() ,然后再調(diào)用 QGraphicsScene::itemAt() 向場景查詢該 Item 。如果想確定 Item 所在視口中的位置,可以在 Item 上調(diào)用 QGraphicsItem::mapToScene() ,然后再調(diào)用視圖的 QGraphicsView::mapFromScene()。最后,如果您想要查看 View 中某個橢圓內(nèi)部的內(nèi)容,可以將 QPainterPath 傳遞給mapToScene() ,然后將映射路徑傳遞給 QGraphicsScene::items()。
您可以通過調(diào)用 QGraphicsItem::mapToScene() 和 QGraphicsItem::mapFromScene() 來映射 Scene 和 Item 之間的坐標和形狀。也通過調(diào)用 QGraphicsItem::mapToParent() 和 QGraphicsItem::mapFromParent() 或通過調(diào)用 QGraphicsItem::mapToItem() 和 QGraphicsItem::mapFromItem() 在父子元素或者不同元素之間映射坐標。所有映射函數(shù)都可以映射點、矩形、多邊形和路徑。
視圖中提供了相同的映射功能,用于 View 與 Scene 之間的相互映射: QGraphicsView::mapFromScene() 和 QGraphicsView::mapToScene() 。要從視圖映射到項目,您首先映射到場景,然后從場景映射到項目。
關鍵特征
縮放和旋轉(zhuǎn)
QGraphicsView 通過 QGraphicsView::setMatrix() 支持與 QPainter 相同的仿射變換能力。 通過對視圖應用變換,可以輕松添加對縮放和旋轉(zhuǎn)的支持。
以下是在 QGraphicsView 子類中實現(xiàn)縮放和旋轉(zhuǎn)槽的示例:
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 屬性
當變換 View 時, QGraphicsView 保持原點的位置
打印
Graphics View 通過其渲染函數(shù) QGraphicsScene::render() 和 QGraphicsView::render() 提供一行打印能力。 這些渲染函數(shù)提供相同的接口:即將 QPainter 傳遞給渲染函數(shù),場景或視圖即會將他們的全部或部分內(nèi)容繪制到繪畫設備中。 此示例顯示如何使用 QPrinter 將整個場景打印到一個頁面中。
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);
}
場景和視圖渲染功能之間的區(qū)別在于:一個在 Scene 坐標中操作,另一個在 View 坐標中。 QGraphicsScene::render() 常常用來打印未變換過的場景,例如繪制幾何數(shù)據(jù)或打印文本文檔; 另一方面,QGraphicsView::render() 更適合打印屏幕截圖,其默認行為是使用提供的繪制設備來精確呈現(xiàn)視口中的內(nèi)容。
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");
當源和目標區(qū)域的大小不匹配時,源內(nèi)容被拉伸以適應目標區(qū)域。 通過將 Qt::AspectRatioMode 傳遞給正在使用的渲染函數(shù),您可以選擇在內(nèi)容拉伸時保持或忽略場景的寬高比。
拖拽
因為 QGraphicsView 間接繼承了QWidget ,所以它已經(jīng)提供了與QWidget 相同的拖放功能。 此外,為了方便起見,Graphics View 框架為Scene 以及 Item 都提供了拖放支持。 當 View 接收到拖拽時,它將拖放事件轉(zhuǎn)換為 QGraphicsSceneDragDropEvent ,然后將其轉(zhuǎn)發(fā)給 Scene ,Scene 接管此事件的調(diào)度,并將其發(fā)送給接受區(qū)域的鼠標下的第一個 Item。
要拖拽 Item,需要先創(chuàng)建一個 QDrag 對象,然后將這個對象傳遞給啟動拖拽的 Widget 。Item 可以被多個 View 觀察,但只有一個 View 可以開始拖動。 在大多數(shù)情況下,拖放是由于按住或移動鼠標而啟動的,因此可以在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();
}
要攔截場景的拖放事件,需要在 QGraphicsItem 子類中重新實現(xiàn)QGraphicsScene::dragEnterEvent() 以及其他場景需要的事件處理程序。 可以在 QGraphicsScene 的每個事件處理程序的文檔中閱讀更多關于 Graphics View 中拖放的信息。
Item 可以通過調(diào)用 QGraphicsItem::setAcceptDrops() 來啟用拖放支持。 要處理傳入的拖動,需要重新實現(xiàn) QGraphicsItem::dragEnterEvent(),QGraphicsItem::dragMoveEvent(),QGraphicsItem::dragLeaveEvent() 和 QGraphicsItem::dropEvent() 。
另請參閱《拖放機器人》示例,以顯示 Graphic View 對拖放操作的支持。
光標和提示信息
像 QWidget 一樣,QGraphicsItem 也支持游標(QGraphicsItem :: setCursor())和工具提示(QGraphicsItem :: setToolTip())。 當鼠標光標進入 Item 的區(qū)域(通過調(diào)用 QGraphicsItem :: contains()進行檢測,游標和工具提示將被 QGraphicsView 激活,。
您還可以通過調(diào)用 QGraphicsView :: setCursor() 直接在視圖上設置默認光標。
另請參閱拖放機器人示例,用于實現(xiàn)工具提示和光標形狀處理的代碼。
動畫
圖形視圖支持多個級別的動畫。 可以使用動畫框架輕松組合各種動畫。 為此, Item 需要繼承 QGraphicsObject 和 QPropertyAnimation 。 QPropertyAnimation 允許將任何 QObject 的屬性動畫化。
另一個選項是創(chuàng)建一個繼承自 QObject 和 QGraphicsItem 的自定義Item, 該 Item 可以設置自己的定時器,并在 QObject :: timerEvent() 中控制動畫。
第三個選項,主要用于與 Qt 3 中的 QCanvas 兼容,通過調(diào)用QGraphicsScene :: advance() 來進一步調(diào)用 QGraphicsScene :: advance() ,這又調(diào)用了 QGraphicsItem :: advance() 。
OpenGL 渲染
要啟用 OpenGL 渲染,您只需通過調(diào)用 QGraphicsView :: setViewport() 將新的 QGLWidget 設置為 QGraphicsView 的視口。 如果您希望 OpenGL 具有抗鋸齒,則需要 OpenGL 示例緩沖區(qū)支持(請參閱QGLFormat :: sampleBuffers() )。
QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
Item 組
通過使 Item 成為另一個 Item 的孩子,可以實現(xiàn)基本的分組功能:所有 Item 將一起移動,所有變換都會從父級到子級傳播。
除此之外,QGraphicsItemGroup 是一個特殊的 Item,它將子事件處理與一個有用的界面相結(jié)合,用于向組中添加和刪除項目。 將項目添加到QGraphicsItemGroup 將保留項目的原始位置和轉(zhuǎn)換,而一般來說,重新啟動項目將導致該子項相對于其新父項重新定位。 為方便起見,您可以通過調(diào)用 QGraphicsScene :: createItemGroup() 通過場景創(chuàng)建QGraphicsItemGroups 。
Widget 與布局
Qt 4.4 通過 QGraphicsWidget 引入了對幾何和布局敏感的 Item 的支持。這個特殊的基本 Item 類似于 QWidget,但它不繼承自 QPaintDevice,而是繼承自 QGraphicsItem 。這允許您編寫具有事件、信號和插槽、大小提示和策略的完整 Widget ,還可以通過QGraphicsLinearLayout 和 QGraphicsGridLayout 布局來管理 Widget 幾何屬性。
QGraphicsWidget
QGraphicsWidget 基于 QGraphicsItem ,提供了兩個最好的功能:QWidget 的額外功能,如樣式,字體,調(diào)色板,布局方向及其幾何特性,以及 QGraphicsItem 的分辨率獨立性和變換支持。因為Graphic View 使用實際坐標而不是整數(shù),所以 QGraphicsWidget 的幾何特性函數(shù)可以處理QRectF 和 QPointF ,這也適用于框架矩形,邊距和間距。因此,使用 QGraphicsWidget ,例如指定內(nèi)容邊距 (0.5,0.5,0.5,0.5) 并不罕見。您可以創(chuàng)建子窗口和“頂級”窗口;在某些情況下,甚至可以使用 Graphics View 創(chuàng)建高級 MDI 應用程序。
支持一些 QWidget 的屬性,包括窗口標志和屬性,但不是全部。您應該參考 QGraphicsWidget 的類文檔,以了支持以及不支持屬性的完整概述。例如,您可以通過將 Qt :: Window 窗口標志傳遞給 QGraphicsWidget 的構(gòu)造函數(shù)來創(chuàng)建裝飾窗口,但是 Graphics View 目前不支持在 macOS 上常見的 Qt :: Sheet 和 Qt :: Drawer標志。
QGraphicsLayout
QGraphicsLayout 是專為 QGraphicsWidget 設計的第二代布局框架的一部分。它的 API 非常類似于 QLayout 。您可以在 QGraphicsLinearLayout 和 QGraphicsGridLayout 內(nèi)部管理 Widgets 和子布局。您也可以通過子類化 QGraphicsLayout 來輕松的編寫自己的布局,或者通過 QGraphicsLayoutItem 的適配器子類將自己的 Item 項添加到布局中。
對嵌入常規(guī) Widget 的支持
Graphics View 提供了將常規(guī) Widget 嵌入到 Scene 中的無縫支持。您可以嵌入簡單的 Widget ,例如 QLineEdit 或 QPushButton ,也可以嵌入復雜的 Widget,如 QTabWidget ,甚至是一個完整的 main window。要將 Widget 嵌入到 Scene 中,只需調(diào)用 QGraphicsScene :: addWidget() ,或者創(chuàng)建一個 QGraphicsProxyWidget 的實例來手動嵌入。
通過 QGraphicsProxyWidget ,Graphics View 能夠深入集成客戶 Widget 的各種功能:包括其光標、工具提示、鼠標、平板電腦和鍵盤事件、子窗口小部件、動畫、彈出窗口(例如 QComboBox 或 QCompleter )以及窗口小部件的輸入焦點和激活。QGraphicsProxyWidget 甚至集成了嵌入式小部件的 tab order,以便您可以用 tab 鍵進入和退出嵌入 widget。您甚至可以在您的場景中嵌入一個新的 QGraphicsView ,以提供復雜的嵌套場景。
在轉(zhuǎn)換嵌入式窗口小部件時,Graphics View 確保窗口小部件獨立轉(zhuǎn)換分辨率,允許字體和樣式在放大時保持清晰。(請注意,獨立性的影響取決于樣式。)
性能
浮點指令
為了準確快速地將變換和效果應用于項目,Graphics View 的構(gòu)建假設是用戶的硬件能夠為浮點指令提供合理的性能。
許多工作站和臺式計算機都配備了適當?shù)挠布砑铀龠@種計算,但是一些嵌入式設備只能提供庫來處理數(shù)學運算或模擬軟件中的浮點指令。
因此,某些設備的某些效果可能比預期的慢。 可以通過在其他領域進行優(yōu)化來彌補這種性能的影響; 例如,通過使用OpenGL渲染場景。 然而,如果任何此類優(yōu)化還依賴于浮點硬件的存在,本身可能會導致性能下降。