1. 程式人生 > >Qt 之圖形檢視框架

Qt 之圖形檢視框架

簡述

圖形檢視(Graphics View)提供了一個平臺,用於大量自定義2D圖元的管理與互動,並提供了一個檢視部件(view widget)來顯示可以縮放和旋轉的圖元。

框架包括一個事件傳播架構,支援場景(Scene)中的圖元(Item)進行精確的雙精度互動功能。圖元可以處理鍵盤事件、滑鼠按下、移動、釋放和雙擊事件,同時也能跟蹤滑鼠移動。

圖形檢視使用一個BSP(Binary Space Partitioning - 二叉空間分割)樹,以提供對圖形元素的快速查詢,正因為如此,它可以使超大的場景實時地視覺化,即使包含數百萬的圖元。

|

圖形檢視架構

圖形檢視提供了一個基於圖元的方式來實現模型檢視(model-view)程式設計,很像InterView中的便利類:QTableView、QTreeView和QListView。多個檢視可以觀察一個單獨的場景,場景則包含了不同的幾何形狀圖元 。

場景

QGraphicsScene提供了圖形檢視場景。

場景有以下職責:

  • 提供一個快速的介面,用於管理大量圖元
  • 向每個圖元傳遞事件
  • 管理圖元的狀態,如:選中、焦點處理
  • 提供未進行座標轉換的渲染功能,主要用於列印

場景是QGraphicsItem物件的容器。呼叫QGraphicsScene::addItem()將圖元新增到場景中後,你就可以通過呼叫場景中的不同的查詢函式來查詢其中的圖元。QGraphicsScene::items()函式及其過載函式可以返回所有圖元,包括:點、矩形、多邊形、通用向量路徑。

QGraphicsScene::itemAt()返回在特定點上最上面的圖元。所有找到的圖元按照層疊遞減的排列順序(即:最先返回的圖元是最頂層的,最後返回的則是最底層的)。

QGraphicsScene scene;
QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));

QGraphicsItem *item = scene.itemAt(50, 50);
// item == rect
  • 1
  • 2
  • 3
  • 4
  • 5

QGraphicsScene的事件傳遞機制負責將場景事件傳遞給圖元,同時也管理圖元之間的傳遞。如果場景在某個位置得到一個滑鼠按下事件,就將該事件傳遞給這個位置上的圖元。

QGraphicsScene同時還管理某些圖元的狀態,例如:圖元的選中和焦點。可以通過呼叫QGraphicsScene::setSelectionArea(),傳遞一個任意形狀,來選中場景中的圖元。此功能也被用於QGraphicsView中橡皮筋(rubberband)選中的基礎。通過呼叫QGraphicsScene::selectedItems()可以獲取當前選中的圖元列表。另外一種由QGraphicsScene處理的狀態是:一個圖元是否有鍵盤輸入焦點。你可以呼叫QGraphicsScene::setFocusItem()或QGraphicsItem::setFocus()為一個圖元設定焦點,或通過QGraphicsScene::focusItem()獲取當前的焦點圖元。

最後,QGraphicsScene允許通過QGraphicsScene::render()將部分場景繪製到繪圖裝置(paint device - 例如:QImage、QPrinter、QWidget)上。可以在本文關於“列印”部分了解更多細節。

檢視

QGraphicsView提供了檢視部件,將一個場景中的內容顯示出來。你可以附加多個檢視到同一個場景,從而針對同一資料集提供幾個視口(viewport)。該檢視部件是一個滾動區域(scroll area),為大型場景瀏覽提供滾動條。如果要啟用OpenGL支援,可通過呼叫QGraphicsView::setViewport(),將一個QGLWidget設定為視口。

QGraphicsScene scene;
myPopulateScene(&scene);

QGraphicsView view(&scene);
view.show();
  • 1
  • 2
  • 3
  • 4
  • 5

檢視通過鍵盤和滑鼠接收輸入事件,並在事件傳送給視覺化的場景之前,將它們轉換成場景事件(將座標轉化為適當的場景座標)。

利用變換矩陣QGraphicsView::transform(),檢視可以轉換場景的座標系,以便實現高階檢視功能,例如:縮放、旋轉。為方便起見,QGraphicsView也提供了檢視和場景座標之間轉換函式:QGraphicsView::mapToScene()和QGraphicsView::mapFromScene()。

這裡寫圖片描述

圖元

QGraphicsItem是場景中圖元的基類。圖形檢視提供了一些典型形狀的標準圖元,例如:矩形 ( QGraphicsRectItem )、橢圓 ( QGraphicsEllipseItem ) 、文字項 ( QGraphicsTextItem )。但當你自定義圖元時,QGraphicsItem強大的特性就體現出來了。除此之外,QGraphicsItem還支援以下特性:

  • 滑鼠按下、移動、釋放和雙擊事件,以及滑鼠懸浮事件、滾輪事件和上下文選單事件。
  • 鍵盤輸入焦點和鍵盤事件。
  • 拖放。
  • 分組:通過父子關係,或QGraphicsItemGroup。
  • 碰撞檢測。

和QGraphicsView一樣,處於區域性座標系下的圖元,也提供了很多函式用於圖元和場景之間、圖元到圖元的座標對映。此外,和QGraphicsView一樣,它可以通過一個矩陣(matrix):QGraphicsItem::transform()來轉換其自身的座標系,這對於旋轉和縮放單個圖元非常有用。

這裡寫圖片描述

圖形檢視框架中的類

這些類提供了一種建立互動式應用程式的框架。

描述
QAbstractGraphicsShapeItem 所有路徑圖元的共同基類
QGraphicsAnchor 表示一個QGraphicsAnchorLayout中兩個圖元之間的anchor
QGraphicsAnchorLayout 佈局可以anchor部件到圖形檢視中
QGraphicsEffect 所有圖形特效的基類
QGraphicsEllipseItem 可以新增到QGraphicsScene的橢圓圖元
QGraphicsGridLayout 圖形檢視中管理部件的網格佈局
QGraphicsItem QGraphicsScene中所有圖元的基類
QGraphicsItemGroup 一個將圖元組當做單個圖元來看待的容器
QGraphicsLayout 圖形檢視中所有佈局類的基類
QGraphicsLayoutItem 可以被繼承,允許佈局類管理的自定義圖元
QGraphicsLineItem 可以新增到QGraphicsScene的直線圖元
QGraphicsLinearLayout 圖形檢視中管理部件的水平或垂直佈局
QGraphicsObject 所有需要訊號、槽、屬性的圖元的基類
QGraphicsPathItem 可以新增到QGraphicsScene的路徑圖元
QGraphicsPixmapItem 可以新增到QGraphicsScene的圖形圖元
QGraphicsPolygonItem 可以新增到QGraphicsScene的多邊形圖元
QGraphicsProxyWidget 代理,用於將一個QWidget物件嵌入到QGraphicsScene中
QGraphicsRectItem 可以新增到QGraphicsScene的矩形圖元
QGraphicsScene 管理大量2D圖元的管理器
QGraphicsSceneContextMenuEvent 圖形檢視框架中的上下文選單事件
QGraphicsSceneDragDropEvent 圖形檢視框架中的拖放事件
QGraphicsSceneEvent 所有圖形檢視相關事件的基類
QGraphicsSceneHelpEvent Tooltip請求時的事件
QGraphicsSceneHoverEvent 圖形檢視框架中的懸停事件
QGraphicsSceneMouseEvent 圖形檢視框架中的滑鼠事件
QGraphicsSceneMoveEvent 圖形檢視框架中的部件移動事件
QGraphicsSceneResizeEvent 圖形檢視框架中的部件大小改變事件
QGraphicsSceneWheelEvent 圖形檢視框架中的滑鼠滾輪事件
QGraphicsSimpleTextItem 可以新增到QGraphicsScene的簡單文字圖元
QGraphicsSvgItem 可以用來呈現SVG檔案內容的QGraphicsItem
QGraphicsTextItem 可以新增到QGraphicsScene的文字圖元,用於顯示格式化文字
QGraphicsTransform 建立QGraphicsItems高階矩陣變換的抽象基類
QGraphicsView 顯示一個QGraphicsScene內容的部件
QGraphicsWidget QGraphicsScene中所有部件圖元的基類
QStyleOptionGraphicsItem 用於描述繪製一個QGraphicsItem所需的引數

圖形檢視座標系

圖形檢視基於笛卡兒座標系,場景中圖元的位置和幾何形狀由兩組資料來表示:X座標和Y座標。當使用一個未轉換的檢視來觀察一個場景,場景中的一個單元將會由場景上的一個畫素表示。

注意 :圖形檢視使用了Qt的座標系,不支援反轉的Y軸座標系(即Y向上為正方向)。

圖形檢視中使用了三種有效的座標系:圖元座標、場景座標、檢視座標。為了簡化實現,圖形檢視提供了非常方便的函式來進行三個座標系的對映。

渲染時,圖形檢視的場景座標對應於QPainter的邏輯座標,檢視座標與裝置座標一致。參考:Coordinate System,瞭解更多關於邏輯座標和裝置座標關係的內容。

這裡寫圖片描述

圖元座標

圖元生活在自己的區域性座標系。它們的座標通常圍繞它們的中心點(0, 0),並且這也是所有轉換的中心。圖元座標系下的幾何元素通常指點、線或矩形。

建立自定義圖元時,只需考慮圖元座標即可。QGraphicsScene和QGraphicsView會為你實現所有相關的轉換,這樣一來,實現自定義圖元就容易多了。例如:當你接收到滑鼠按下或拖拽事件時,事件位置將由圖元座標給出。如果某一點(傳遞一個圖元座標作為引數)在圖元中,那麼GraphicsItem::contains()虛擬函式將會返回true;否則,返回false。同樣的,項繫結的矩形或形狀區域也是項座標系統的。同樣的,圖元的矩形邊界和形狀都是基於圖元座標的。

圖元的位置是圖元的中心點在其父座標系下的座標,有時也被稱為父座標。場景從這個意義上說是所有無父圖元的“parent”,頂層圖元的位置在場景座標中。

子座標是相對於父座標而言的。如果子座標沒有轉換,那麼子座標和父座標的差異就和圖元在父座標中的距離一樣。例如:一個未經轉換的子圖元正好位於父圖元的中心點,那麼,這兩個圖元的座標系是完全一樣的。如果子圖元的位置是(10, 0),那麼子圖元的(0, 10)點就對應父圖元的(10, 10)點位置。

由於圖元的位置和轉換是相對於父圖元來說的,因此,雖然父圖元的轉換隱式地轉換了子圖元,但是子圖元的座標不會因父圖元的轉換而受到影響。在上述示例中,即使父圖元經過了旋轉和縮放,子圖元的(0, 10)點始終對應父圖元的(10, 10)點。不過相對於場景來說,子圖元將隨著父圖元進行轉換和偏移 。如果父圖元縮放了(2x, 2x),那麼子圖元在場景中的座標是(20, 0),並且其(10, 0)點將會對應於場景中的(40, 0)點。

不管圖元或父圖元進行了怎樣的轉換,QGraphicsItem的函式操作都在圖元座標內。例如:一個圖元的矩形邊界(QGraphicsItem::boundingRect())總是在圖元座標下給出。但是QGraphicsItem::pos()是例外之一,該函式表示其在父圖元中的位置 。

場景座標

場景為所有的圖元提供了基礎的座標系。場景座標系描述了每一個頂層圖元的位置,同時構成了從檢視傳遞到場景的所有場景事件的基礎。場景中的每個圖元都有一個場景位置和矩形邊界(QGraphicsItem::scenePos()、QGraphicsItem::sceneBoundingRect());另外,也有其自身的位置和矩形邊界。場景位置描述了圖元在場景座標下內的位置,場景矩形邊界則提供給QGraphicsScene來決定場景中的哪塊區域已經被改變了。場景中的變化通過QGraphicsScene::changed()訊號發出,引數是場景矩形列表。

檢視座標

檢視座標是部件的座標,檢視座標中的每個單位對應一個畫素。對於該座標系來說比較特殊的一點是:它相對於部件或視口,不會受被觀察的場景所影響。QGraphicsView的視口左上角總是(0, 0),右下角總是(viewport width, viewport height)。所有的滑鼠事件和拖拽事件都以檢視座標接收到的,你需要將這些座標對映到場景,以便於和圖元進行互動。

座標對映

通常處理場景中的圖元時,從場景到圖元、從圖元到圖元、從檢視到場景的座標或任意形狀轉換將會非常有用。例如:當在QGraphicsView視口中點選滑鼠,你可以向場景詢問當前滑鼠下方的是什麼圖元(呼叫 QGraphicsView::mapToScene()轉換座標,然後通過QGraphicsScene::itemAt()查詢圖元)。如果想知道一個圖元處於視口中的位置,可以呼叫圖元的函式QGraphicsItem::mapToScene(),然後再呼叫檢視的函式QGraphicsView::mapFromScene()。最後,如果想查詢位於一個橢圓區域內的圖元,你可以把一個QPainterPath傳遞給mapToScene(),然後將轉換後的path傳遞給QGraphicsScene::items()。

通過呼叫QGraphicsItem::mapToScene()將座標或任意形狀對映到圖元的場景中去,而呼叫QGraphicsItem::mapFromScene()將其映射回來;通過呼叫QGraphicsItem::mapToParent()將圖元對映到父圖元,而呼叫QGraphicsItem::mapFromParent()將其映射回來;甚至可以呼叫QGraphicsItem::mapToItem()和QGraphicsItem::mapFromItem()在不同的圖元間進行對映。所有的對映函式均支援點、矩形、多邊形和路徑。

在檢視和場景之間也存在著同樣的對映函式:QGraphicsView::mapFromScene()和QGraphicsView::mapToScene()。要從檢視對映到圖元,第一步是對映到場景,然後才能從場景對映到圖元。

主要特點

縮放和旋轉

和QPainter一樣,QGraphicsView也可以通過QGraphicsView::setMatrix()支援仿射轉換。通過將轉換應用到檢視上,可以很輕鬆地新增對普通瀏覽的支援,例如:縮放和旋轉。

下面的示例,說明了如何通過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); }
    ...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

槽可以關聯到啟用了“autoRepeat”屬性的QToolButtons。

在轉換檢視過程中,QGraphicsView始終保持與檢視中心對齊。

參考:Elastic Nodes Example,瞭解更多關於縮放的內容。

列印

圖形檢視通過其渲染函式QGraphicsScene::render()和QGraphicsView::render(),提供了非常簡單的列印功能。

這兩個函式提供了相同的API:只需要將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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

場景和檢視繪製函式的區別在於:前者操作的是場景座標,後者操作的則是檢視座標。QGraphicsScene::render()多用於列印一個未轉換的場景各部分,例如:列印幾何資料圖表或文字文件。 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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

當源區域和目標區域的大小不匹配時,源區域內容將會被縮放來適應目標區域。通過傳遞Qt::AspectRatioMode引數給你使用的渲染函式,在內容被縮放時,可以選擇保持或忽略場景的縱橫比。

拖放

由於QGraphicsView間接繼承了QWidget,因此QGraphicsView也提供了和QWidget一樣的拖放功能。此外,為方便起見,圖形檢視框架為場景、每個圖元提供了拖放支援。當檢視接收到一個拖拽動作,它將拖放事件轉換為一個QGraphicsSceneDragDropEvent,然後將其轉發給場景。場景則會接管該事件的排程,並將其傳送給滑鼠下面第一個接受放下動作的圖元。

要拖拽一個圖元,只需要建立一個QDrag物件,將指標傳給開始拖拽的部件。圖元可以同時被多個檢視觀察,但是隻有一個檢視可以進行拖拽。在大多數情況下,拖拽都從滑鼠按下或移動開始,因此在 mousePressEvent()或mouseMoveEvent()事件中,你可以從事件中拿到原始的部件指標,例如:

void CustomItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    QMimeData *data = new QMimeData;
    data->setColor(Qt::green);

    QDrag *drag = new QDrag(event->widget());
    drag->setMimeData(data);
    drag->start();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

要攔截場景中的拖放事件,需要實現QGraphicsScene::dragEnterEvent(),選擇你需要處理的事件,然後進行相應處理即可。你可以到 QGraphicsScene的文章中檢視更多關於拖放的內容。

圖元通過呼叫QGraphicsItem::setAcceptDrops()來啟用對拖放的支援;如果要處理拖動,需要實現 QGraphicsItem::dragEnterEvent()、QGraphicsItem::dragMoveEvent()、QGraphicsItem::dragLeaveEvent()、QGraphicsItem::dropEvent(),這幾個事件。

參考:Drag and Drop Robot example,瞭解更多關於圖形檢視拖拽的內容。

游標和tooltip

和QWidget一樣,QGraphicsItem也支援設定游標(QGraphicsItem::setCursor())和tooltip(QGraphicsItem::setToolTip())。當滑鼠游標進入item區域(由QGraphicsItem::contains()檢測)時,游標和tooltip就會被QGraphicsView啟用。

你也可以通過呼叫QGraphicsView::setCursor(),直接為檢視設定一個預設的游標。

參考:Drag and Drop Robot example,瞭解更多關於tooltip和游標形狀操作的內容。

動畫

圖形檢視在幾個層面上提供了對動畫的支援。你可以用Animation Framework輕鬆地設定動畫:只需要讓你的圖元從QGraphicsObject繼承,然後將QPropertyAnimation繫結到上面。QPropertyAnimation可以為任何QObject屬性實現動畫效果。

另外一個選擇是:建立一個自定義圖元,從QObject和QGraphicsItem繼承。該圖元可以設定自己的定時器,然後在QObject::timerEvent()中控制動畫。

第三個選擇僅限於與Qt3中的QCanvas相容。呼叫QGraphicsScene::advance()從而會依次呼叫 QGraphicsItem::advance()。

OpenGL渲染

要啟用OpenGL渲染,只要簡單地呼叫QGraphicsView::setViewport()來設定一個新的QGLWidget作為QGraphicsView的視口。如果你希望OpenGL具有反鋸齒,則需要OpenGL支援取樣緩衝(參考:QGLFormat::sampleBuffers())。

示例:

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

圖元組

通過將一個圖元設定為另一個圖元的子圖元,就可以得到圖元組最重要的功能:圖元會一起移動,所有轉換都會從父圖元傳播到子圖元中。

此外,QGraphicsItemGroup是一個特殊的圖元,它提供了對子圖元事件的支援,同時還提供了用於新增和刪除子圖元的介面。將一個圖元新增到QGraphicsItemGroup將保持圖元原始的位置和座標轉換,不過重新設定圖元的父圖元則會導致圖元重新定位到相對於父圖元的位置。為了方便起見,你可以呼叫QGraphicsScene::createItemGroup()來建立QGraphicsItemGroup圖元。

部件和佈局

Qt4.4通過QGraphicsWidget引入了對幾何體和對佈局敏感的圖元的支援。這一特殊的基類圖元和QWidget類似,但是不像QWidget,它沒有從QPaintDevice繼承,而是從QGraphicsItem。這樣就允許你完全實現能夠處理事件、訊號與槽、大小調整和策略的部件,同時你還可以通過QGraphicsLinearLayout和QGraphicsGridLayout來管理部件的幾何元素。

QGraphicsWidget

QGraphicsWidget建立在QGraphicsItem之上,具有QGraphicsItem的所有功能,保持了較小的資源佔用,同時提供了兩者的優勢:來自QWidget的額外的功能,例如:樣式、字型、調色盤、佈局、幾何形狀,來自QGraphicsItem的解析度獨立性和座標轉換的支援。由於圖形檢視使用真實的座標而不是整數,因此 QGraphicsWidget的幾何形狀函式可以同時操作QRectF和QPointF。同時也能應用到邊框的大小、邊距和間距上,例如:對於QGraphicsWidget,規定內容邊距為(0.5, 0.5, 0.5, 0.5)是非常常見的。例如:你可以建立子部件,甚至是“頂級”視窗。在某些情況下,你甚至可以將圖形檢視用於高階多文件介面的應用程式。

QGraphicsWidget支援部分QWidget屬性,包括視窗標誌位(window flags)和屬性,但是並非全部支援。可以參考QGraphicsWidget文件,以獲取完整列表來判斷哪些支援以及哪些不支援。例如:你可以在建立QGraphicsWidget時賦予Qt::Window標誌,從而得到封裝的視窗,但是圖形檢視目標並不支援 Qt::Sheet和Qt::Drawer標誌,這兩者在Mac Os X上非常常見。

QGraphicsLayout

QGraphicsLayout是第二代佈局框架的內容之一,專門為QGraphicsWidget設計。其API和QLayout非常相似。你可以在QGraphicsLinearLayout或QGraphicsGridLayout中對部件或者子佈局進行管理,也可以通過派生QGraphicsLayout實現你自己的佈局類,還可以通過派生QGraphicsLayoutItem來實現你自己的介面卡,從而將QGraphicsItem圖元加入到佈局中。

嵌入式部件支援

圖形檢視對將任何部件嵌入到場景中提供無縫的支援。你可以嵌入簡單的部件,例如:QLineEdit或QPushButton,也可以是複雜的部件,例如:QTabWidget,甚至是完整的主視窗。要將部件嵌入場景中,只需要簡單地呼叫QGraphicsScene::addWidget()或者建立一個QGraphicsProxyWidget物件並將部件手工的嵌入其中。

通過QGraphicsProxyWidget圖形檢視能夠完全繼承客戶端部件特性,包括:它的滑鼠游標、tooltip、滑鼠事件、平板電和鍵盤事件、子視窗、動畫、彈出(例如:QComboBox或QCompleter),以及部件的輸入焦點和啟用狀態。QGraphicsProxyWidget甚至集成了嵌入式部件的tab切換順序,這樣你就可以通過tab鍵讓焦點進入或者移出嵌入式部件。你甚至可以嵌入一個新的 QGraphicsView到你的場景中,從而提供複雜的巢狀的檢視。

當改變一個嵌入式部件,圖形檢視可以確保部件轉換時與解析度無關,當放大時使字型和樣式看起來乾淨利落。(注意:解析度無關的效果取決於風格。)

效能

浮點指令

為了精確和快速的將座標轉換和特效應用到圖元上,圖形檢視在編譯的時候預設使用者的硬體能夠為浮點指令提供合理的效能。

很多工作站和桌面電腦都配備了適當的硬體來加速這種型別的計算,但是一些嵌入式裝置可能僅僅提供了處理數學運算的庫,或者需要用軟體來模擬浮點指令。

這樣,在某些裝置上,某些型別的特效可能要比預期的慢。有可能可以在其它方面進行優化來彌補效能上的損失,例如:用OpenGL來繪製場景。不過,如果優化本身是依賴於浮點計算硬體的話,可能都會帶來效能上的損失。