Qt之圖形視圖框架

簡述

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

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

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

圖形視圖架構

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

場景

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

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();

視圖經過鍵盤和鼠標接收輸入事件,並在事件發送給可視化的場景以前,將它們轉換成場景事件(將座標轉化爲適當的場景座標)。

利用變換矩陣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); }
    ...
};

槽能夠關聯到啓用了「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);
}

場景和視圖繪製函數的區別在於:前者操做的是場景座標,後者操做的則是視圖座標。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");

當源區域和目標區域的大小不匹配時,源區域內容將會被縮放來適應目標區域。經過傳遞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();
}

要攔截場景中的拖放事件,須要實現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)));

圖元組

經過將一個圖元設置爲另外一個圖元的子圖元,就能夠獲得圖元組最重要的功能:圖元會一塊兒移動,全部轉換都會從父圖元傳播到子圖元中。

此外,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來繪製場景。不過,若是優化自己是依賴於浮點計算硬件的話,可能都會帶來性能上的損失。

更多參考

  • Graphics View Framework - 助手
相關文章
相關標籤/搜索