Graphics View 架構

The Graphic View 提供了一個 Surface,用于管理和交互大量定制的 2D 圖形化 Item;同時還提供了一個用于可視化這些 Items 的 View 組件,這個 View 支持縮放和旋轉。

該框架包括事件傳播架構,可以為場景中的 Item 提供精確的雙精度交互能力。 Item 可以處理鍵盤事件,鼠標的按壓、釋放、移動和雙擊事件,也可以跟蹤鼠標移動。

Graphics View 使用 BSP(二叉搜索樹)提供非常快速的 Item 查找,因此,它可以實時顯示大型場景,即使包含了數百萬個 Item。

Graphics View 在 Qt 4.2 中引入,取代了其前身 QCanvas

Graphic View 的架構

Graphics View 提供了一種基于 Item 的 Model-View 編程方法,和 InterView 中的便捷類 QTableViewQTreeView QListView 一樣,多個 View 可以觀察單個 Scene,Scene 中包含不同幾何形狀的 Item。

The Scene

QGraphicsScene 提供了 Graphic View 的場景,場景有以下職責:

  • 提供一個高性能的接口來管理大量的 Items
  • 將事件傳播到每個 Item
  • 管理 Item 狀態,如選擇和焦點處理
  • 提供未被變換的渲染能力,主要用于打印

Scene 作為 QGraphicsItem 對象的容器,通過調用 QGraphicsScene::addItem() 將 Item 添加到 Scene 中。還有許多 Item 查找函數,QGraphicsScene::items() 有多個重載版本,可以返回返回由點、矩形、多邊形或向量路徑包含或相交的所有 Items 。 QGraphicsScene::itemAt() 返回特定點的最上面的 Item 。 所有 Item 查找函數以降序堆疊順序返回找到的 Item(即,第一個返回的 Item 是最上面的,最后一個 Item 是最底部的)。

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 將傳遞事件到該位置的那個 Item。

QGraphicsScene 還管理某些 Item 狀態:如 Item 的選擇和焦點。 您可以通過調用 QGraphicsScene::setSelectionArea() 傳遞一個任意形狀來選擇 Scene 中的 Item。 這個函數也是在 QGraphicsView 中進行橡皮筋選擇的基礎。 要獲取所有當前選中的 Item,可以調用 QGraphicsScene::selectedItems()QGraphicsScene 處理的另一個狀態是 Item 是否具有鍵盤輸入焦點,可以通過調用 QGraphicsScene::setFocusItem()QGraphicsItem::setFocus() 來為 Item 設置焦點,或通過調用 QGraphicsScene::focusItem() 獲取當前的有焦點的 Item。

最后,QGraphicsScene 允許您通過 QGraphicsScene::render() 函數將 Scene 的一部分渲染到繪圖設備中。 您可以在本文檔后面的“打印”部分中閱讀更多信息。

The View

QGraphicsView 提供了 View 組件,用于可視化場景中的內容。 您可以將多個 View 附加到同一個 Scene 上,相當于將同一個數據集提供給多個視口。View 組件也是一個滾動區域,并提供用于瀏覽大型場景的滾動條。 要啟用 OpenGL 支持,可以通過調用 QGraphicsView::setViewport()QGLWidget 設置為視口。

QGraphicsScene scene;
myPopulateScene(&scene);

QGraphicsView view(&scene);
view.show();

視圖可以從鍵盤和鼠標接收事件,并將這些事件轉換為場景事件(同時將坐標轉換為場景坐標),然后將其發送給場景。

使用變換矩陣和 QGraphicsView::transform() ,視圖可以變換場景的坐標系。這樣可以實現一些高級導航功能:如縮放和旋轉。為方便起見,QGraphicsView 還提供了視圖和場景之間的坐標映射函數:QGraphicsView::mapToScene()QGraphicsView::mapFromScene()

image

The Item

QGraphicsItem 是場景中圖形化 Item 的基類。Graphic View 提供了幾個典型形狀的標準 Item,如矩形(QGraphicsRectItem),橢圓(QGraphicsEllipseItem)和文本項(QGraphicsTextItem)。當您編寫自定義 Item 時,QGraphicsItem 的強力特性都是可用的。除此之外,QGraphicsItem 還支持以下功能:

  • 鼠標按下,移動,釋放和雙擊事件,以及鼠標懸停事件,滾輪事件和上下文菜單事件。
  • 鍵盤輸入焦點和按鍵事件
  • 拖放
  • 通過父子關系和 QGraphicsItemGroup 進行分組
  • 碰撞檢測

每個 Item 都有自己的本地坐標系,像 QGraphicsView 一樣,它還提供許多函數,用于在 Item 和 Scene 之間,以及在 Item 和 Item 之間映射坐標。此外,像 QGraphicsView 一樣,它也有使用矩陣來變換坐標系的函數:QGraphicsItem::transform(),這對于旋轉和縮放各個獨立的 Item 很有用。

Item 可以包含其他子 Item。父 Item 的變換會被其所有子Item 繼承。除了變換會被累積, Item 的所有其他函數(例如,QGraphicsItem::contains()QGraphicsItem::boundingRect()QGraphicsItem :: collidesWith() 仍然在本地坐標中運行。

Item 可以通過 QGraphicsItem::shape() 函數進行碰撞檢測,這個函數和 QGraphicsItem::collidesWith() 都是虛函數。利用 QGraphicsItem::shape() 返回的 Item 形狀作為 QPainterPath 的局部坐標系,Item 可以處理所有的碰撞檢測。但是,如果要提供自己的碰撞檢測,可以重新實現 QGraphicsItem::collidesWith()

The Graphics View 的坐標系

Graphics View 基于笛卡爾坐標系; Item 在場景中的位置和形狀由兩組數字表示: x 坐標和 y 坐標。 當使用沒有進行過坐標變換的視圖觀察場景時,場景上的一個單位就是屏幕上的一個像素。

注意:Graphics VIew 使用 Qt 的坐標系,不支持倒置的Y軸坐標系(即 y 向上增長)。

這樣,Graphics View 中就有三套的坐標系:Item 坐標、Scene 坐標和 View 坐標。 為了簡化您的實現,Graphics View 提供了便于三個坐標系之間進行映射的函數。

渲染時,Scene 坐標對應于 QPainter 的邏輯坐標,View 坐標相當于設備坐標。 在《坐標系統》文檔中,您可以閱讀有關邏輯坐標和設備坐標之間的關系。

Item 坐標系

Item 有自己的本地坐標系,坐標原點為 (0, 0) ,這也是所有變換的原點。 Item 坐標系中的幾何圖元通常被稱為點、線或矩形。

當創建自定義 Item 時,唯一需要操心的就是 Item 坐標系; QGraphicsSceneQGraphicsView 會執行所有變換,這使得實現自定義項目很容易。例如,如果您接收到鼠標點擊或拖動事件,事件位置將以 Item 坐標系給出。 QGraphicsItem::contains() 函數接收一個 Item 坐標系中的坐標參數,如果這個坐標在 Item 上則返回 true,否則返回 false 。類似地,Item 的邊界和形狀也是在 Item 坐標系中描述的。

Item 的位置是 Item 原點在其父系坐標系中的坐標,稱為父坐標。Scene 被認為是所有無父元素的 Item 的 Parent,因此,頂層 Item 的位置在 Scene 坐標系中描述。

要將子元素的座標映射到父元素的坐標系中,需要經過一番計算,例如:沒有變換的子元素的原點如果精確位于其父元素的原點上,則父子兩個 Item 的坐標系將相同;然而,如果子元素原點的位置在父坐標系中是(10,0) ,則子元素坐標系中的 (0,10) 在父坐標系中為 (10,10)

由于子元素的坐標系只參照父元素,因此子元素的坐標系不受父元素變換的影響。在上面的例子中,即使父元素被旋轉和縮放,子元素中的(0,10) 點映射到父元素坐標系中仍然是 (10,10) 點。然而,在 Scene 坐標系中,如果 Scene 坐標系發生了變換和重新定位。如 Scene 被縮放 (2x,2x) ,則子元素的 (0, 0) 位置在 Scene 坐標系中變為 (20,0) ,且其 (10,0) 點在 Scene 坐標系中變為 (40,0) 。Scene 坐標系可以看做是世界坐標系。

除了 QGraphicsItem::pos() 等少數例外,其他 QGraphicsItem 的函數都是在 Item 坐標系中操作,而不考慮 Item 自身或者其父元素的變換。例如,Item 的邊界矩形(即 QGraphicsItem::boundingRect())總是在 Item 坐標坐標系中。

Scene 坐標系

Scene 坐標系是 Item 的基準坐標系,每個頂層 Item 都是定位在 Scene 坐標系中,從 View 傳遞到 Scene 的所有事件也在 Scene 中定位。 Scene 中的每個 Item 除了在本地坐標系中有定位和邊界矩形之外,在場景坐標系中也具有場景位置 "scene position" 和邊界矩形(QGraphicsItem::scenePos()QGraphicsItem::sceneBoundingRect())。場景位置描述 Item 在場景坐標系中的位置,而場景邊界矩可以讓 Scene 確定場景的哪些區域已經發生了改變。 Scene 中的變化通過 QGraphicsScene::changed() 信號傳遞,參數是場景矩形的列表。

View 坐標系

View 坐標系是組件的坐標系,View 坐標系中的每個單位對應于一個像素。 View 坐標系的全部控件就是組件窗口或者視口,它不受所觀察的 Scene 影響。GraphicsView 視口的左上角始終為 (0,0) ,右下角始終為 (視口寬度,視口高度) 。 所有的鼠標事件和拖放事件最初發生在 View 坐標系中,您需要將這些坐標映射到 Scene 中以便與 Item 進行交互。

坐標系映射

在處理場景中的 Item 時,常常需要將坐標和形狀從 Scene 映射到 Item、或者從一個 Item 映射到另一個 Item,或者從 View 映射到 Scene 。例如,當您在 QGraphicsView 的視口中單擊鼠標時,您可以通過調用 QGraphicsView::mapToScene() ,然后再調用 QGraphicsScene::itemAt() 向場景查詢該 Item 。如果想確定 Item 所在視口中的位置,可以在 Item 上調用 QGraphicsItem::mapToScene() ,然后再調用視圖的 QGraphicsView::mapFromScene()。最后,如果您想要查看 View 中某個橢圓內部的內容,可以將 QPainterPath 傳遞給mapToScene() ,然后將映射路徑傳遞給 QGraphicsScene::items()

您可以通過調用 QGraphicsItem::mapToScene()QGraphicsItem::mapFromScene() 來映射 Scene 和 Item 之間的坐標和形狀。也通過調用 QGraphicsItem::mapToParent()QGraphicsItem::mapFromParent() 或通過調用 QGraphicsItem::mapToItem()QGraphicsItem::mapFromItem() 在父子元素或者不同元素之間映射坐標。所有映射函數都可以映射點、矩形、多邊形和路徑。

視圖中提供了相同的映射功能,用于 View 與 Scene 之間的相互映射: QGraphicsView::mapFromScene()QGraphicsView::mapToScene() 。要從視圖映射到項目,您首先映射到場景,然后從場景映射到項目。

關鍵特征

縮放和旋轉

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); }
      ...
  };

這些槽可以被 QToolButtons 連接,并開啟 autoRepeat 屬性

當變換 View 時, QGraphicsView 保持原點的位置

打印

Graphics View 通過其渲染函數 QGraphicsScene::render()QGraphicsView::render() 提供一行打印能力。 這些渲染函數提供相同的接口:即將 QPainter 傳遞給渲染函數,場景或視圖即會將他們的全部或部分內容繪制到繪畫設備中。 此示例顯示如何使用 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);
  }

場景和視圖渲染功能之間的區別在于:一個在 Scene 坐標中操作,另一個在 View 坐標中。 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");

當源和目標區域的大小不匹配時,源內容被拉伸以適應目標區域。 通過將 Qt::AspectRatioMode 傳遞給正在使用的渲染函數,您可以選擇在內容拉伸時保持或忽略場景的寬高比。

拖拽

因為 QGraphicsView 間接繼承了QWidget ,所以它已經提供了與QWidget 相同的拖放功能。 此外,為了方便起見,Graphics View 框架為Scene 以及 Item 都提供了拖放支持。 當 View 接收到拖拽時,它將拖放事件轉換為 QGraphicsSceneDragDropEvent ,然后將其轉發給 Scene ,Scene 接管此事件的調度,并將其發送給接受區域的鼠標下的第一個 Item。

要拖拽 Item,需要先創建一個 QDrag 對象,然后將這個對象傳遞給啟動拖拽的 Widget 。Item 可以被多個 View 觀察,但只有一個 View 可以開始拖動。 在大多數情況下,拖放是由于按住或移動鼠標而啟動的,因此可以在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 子類中重新實現QGraphicsScene::dragEnterEvent() 以及其他場景需要的事件處理程序。 可以在 QGraphicsScene 的每個事件處理程序的文檔中閱讀更多關于 Graphics View 中拖放的信息。

Item 可以通過調用 QGraphicsItem::setAcceptDrops() 來啟用拖放支持。 要處理傳入的拖動,需要重新實現 QGraphicsItem::dragEnterEvent()QGraphicsItem::dragMoveEvent()QGraphicsItem::dragLeaveEvent()QGraphicsItem::dropEvent()

另請參閱《拖放機器人》示例,以顯示 Graphic View 對拖放操作的支持。

光標和提示信息

QWidget 一樣,QGraphicsItem 也支持游標(QGraphicsItem :: setCursor())和工具提示(QGraphicsItem :: setToolTip())。 當鼠標光標進入 Item 的區域(通過調用 QGraphicsItem :: contains()進行檢測,游標和工具提示將被 QGraphicsView 激活,。

您還可以通過調用 QGraphicsView :: setCursor() 直接在視圖上設置默認光標。

另請參閱拖放機器人示例,用于實現工具提示和光標形狀處理的代碼。

動畫

圖形視圖支持多個級別的動畫。 可以使用動畫框架輕松組合各種動畫。 為此, Item 需要繼承 QGraphicsObjectQPropertyAnimationQPropertyAnimation 允許將任何 QObject 的屬性動畫化。

另一個選項是創建一個繼承自 QObjectQGraphicsItem 的自定義Item, 該 Item 可以設置自己的定時器,并在 QObject :: timerEvent() 中控制動畫。

第三個選項,主要用于與 Qt 3 中的 QCanvas 兼容,通過調用QGraphicsScene :: advance() 來進一步調用 QGraphicsScene :: advance() ,這又調用了 QGraphicsItem :: advance()

OpenGL 渲染

要啟用 OpenGL 渲染,您只需通過調用 QGraphicsView :: setViewport() 將新的 QGLWidget 設置為 QGraphicsView 的視口。 如果您希望 OpenGL 具有抗鋸齒,則需要 OpenGL 示例緩沖區支持(請參閱QGLFormat :: sampleBuffers() )。

QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));

Item 組

通過使 Item 成為另一個 Item 的孩子,可以實現基本的分組功能:所有 Item 將一起移動,所有變換都會從父級到子級傳播。

除此之外,QGraphicsItemGroup 是一個特殊的 Item,它將子事件處理與一個有用的界面相結合,用于向組中添加和刪除項目。 將項目添加到QGraphicsItemGroup 將保留項目的原始位置和轉換,而一般來說,重新啟動項目將導致該子項相對于其新父項重新定位。 為方便起見,您可以通過調用 QGraphicsScene :: createItemGroup() 通過場景創建QGraphicsItemGroups

Widget 與布局

Qt 4.4 通過 QGraphicsWidget 引入了對幾何和布局敏感的 Item 的支持。這個特殊的基本 Item 類似于 QWidget,但它不繼承自 QPaintDevice,而是繼承自 QGraphicsItem 。這允許您編寫具有事件、信號和插槽、大小提示和策略的完整 Widget ,還可以通過QGraphicsLinearLayoutQGraphicsGridLayout 布局來管理 Widget 幾何屬性。

QGraphicsWidget

QGraphicsWidget 基于 QGraphicsItem ,提供了兩個最好的功能:QWidget 的額外功能,如樣式,字體,調色板,布局方向及其幾何特性,以及 QGraphicsItem 的分辨率獨立性和變換支持。因為Graphic View 使用實際坐標而不是整數,所以 QGraphicsWidget 的幾何特性函數可以處理QRectFQPointF ,這也適用于框架矩形,邊距和間距。因此,使用 QGraphicsWidget ,例如指定內容邊距 (0.5,0.5,0.5,0.5) 并不罕見。您可以創建子窗口和“頂級”窗口;在某些情況下,甚至可以使用 Graphics View 創建高級 MDI 應用程序。

支持一些 QWidget 的屬性,包括窗口標志和屬性,但不是全部。您應該參考 QGraphicsWidget 的類文檔,以了支持以及不支持屬性的完整概述。例如,您可以通過將 Qt :: Window 窗口標志傳遞給 QGraphicsWidget 的構造函數來創建裝飾窗口,但是 Graphics View 目前不支持在 macOS 上常見的 Qt :: SheetQt :: Drawer標志

QGraphicsLayout

QGraphicsLayout 是專為 QGraphicsWidget 設計的第二代布局框架的一部分。它的 API 非常類似于 QLayout 。您可以在 QGraphicsLinearLayoutQGraphicsGridLayout 內部管理 Widgets 和子布局。您也可以通過子類化 QGraphicsLayout 來輕松的編寫自己的布局,或者通過 QGraphicsLayoutItem 的適配器子類將自己的 Item 項添加到布局中。

對嵌入常規 Widget 的支持

Graphics View 提供了將常規 Widget 嵌入到 Scene 中的無縫支持。您可以嵌入簡單的 Widget ,例如 QLineEditQPushButton ,也可以嵌入復雜的 Widget,如 QTabWidget ,甚至是一個完整的 main window。要將 Widget 嵌入到 Scene 中,只需調用 QGraphicsScene :: addWidget() ,或者創建一個 QGraphicsProxyWidget 的實例來手動嵌入。

通過 QGraphicsProxyWidget ,Graphics View 能夠深入集成客戶 Widget 的各種功能:包括其光標、工具提示、鼠標、平板電腦和鍵盤事件、子窗口小部件、動畫、彈出窗口(例如 QComboBoxQCompleter )以及窗口小部件的輸入焦點和激活。QGraphicsProxyWidget 甚至集成了嵌入式小部件的 tab order,以便您可以用 tab 鍵進入和退出嵌入 widget。您甚至可以在您的場景中嵌入一個新的 QGraphicsView ,以提供復雜的嵌套場景。

在轉換嵌入式窗口小部件時,Graphics View 確保窗口小部件獨立轉換分辨率,允許字體和樣式在放大時保持清晰。(請注意,獨立性的影響取決于樣式。)

性能

浮點指令

為了準確快速地將變換和效果應用于項目,Graphics View 的構建假設是用戶的硬件能夠為浮點指令提供合理的性能。

許多工作站和臺式計算機都配備了適當的硬件來加速這種計算,但是一些嵌入式設備只能提供庫來處理數學運算或模擬軟件中的浮點指令。

因此,某些設備的某些效果可能比預期的慢。 可以通過在其他領域進行優化來彌補這種性能的影響; 例如,通過使用OpenGL渲染場景。 然而,如果任何此類優化還依賴于浮點硬件的存在,本身可能會導致性能下降。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 簡述 圖形視圖(Graphics View)提供了一個平臺,用于大量自定義2D圖元的管理與交互,并提供了一個視圖部...
    YBshone閱讀 5,373評論 0 5
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,523評論 25 708
  • View Geometry and Coordinate Systems 二、視圖幾何和坐標系統 The defa...
    大灰很閱讀 972評論 0 1
  • 2016-03-04 寶木大仙 淺白軒 午后陽光正好,照在窗前的地上明晃晃的一片光亮,小女孩甚至覺得,那可能是一片...
    寶木大仙閱讀 439評論 0 1
  • 前言 首先我要感謝粉絲,讀者和王小二讀書寫作俱樂部成員給予我的支持!給了我繼續寫作下去的勇氣…… 文化基礎方面的弱...
    楊殿國閱讀 769評論 1 5