天天看點

Qt 2D繪圖:圖形視圖架構的結構和坐标系統

作者:C加加Qt技術開發老傑

一、圖形視圖架構的結構

在前面講的基本繪圖中,我們可以自己繪制各種圖形,并且控制它們。但是,如果需要同時繪制很多個相同或不同的圖形,并且要控制它們的移動、檢測它們的碰撞和疊加;或者我們想讓自己繪制的圖形可以拖動位置、進行縮放和旋轉等操作。實作這些功能,要是還使用以前的方法,那麼會十分困難。解決這些問題,可以使用Qt提供的圖形視圖架構。

圖形視圖(Graphics View)架構結構的主要特點如下:

  • 圖形視圖(Graphics View)可以對大量定制的2D圖形項進行管理和互相作用。視圖部件可以讓所有圖形項可視化,它還提供了縮放和旋轉功能。
  • 架構中包含了一個事件傳播構架,提供了和場景中的圖形項進行精确的雙精度互動的能力,圖形項可以處理鍵盤事件,滑鼠的按下、移動、釋放和輕按兩下事件,還可以跟蹤滑鼠的移動。
  • 圖形視圖架構使用一個BSP(Binary Space Partitioning)樹來快速發現圖形項,也正是因為如此,它可以實時顯示一個巨大的場景,甚至包含上百萬個圖形項。
  • 圖形視圖架構結構中,系統可以利用Qt繪圖系統的反鋸齒、OpenGL工具來改善繪圖性能。

圖形視圖結構主要包含三部分:

  • 場景(Scene) :QGraphicsScene類
  • 視圖(View) :QGraphicsView類
  • 圖形項(Item):QGraphicsItem類
Qt 2D繪圖:圖形視圖架構的結構和坐标系統

Qt開發必備技術棧學習路線和資料

1.1 場景(Scene)

場景是圖形項QGraphicsItem對象的容器,其主要完成的工作包括:

(1)提供用于管理大量圖形項的快速接口;

(2)傳播事件給每一個圖形項;

(3)管理圖形項的狀态(如選擇和焦點處理);

(4)提供無變換的渲染功能,主要用于列印。

下面是一些QGraphicsScene的常用函數:

  • 可以調用QGraphicsScene: :addItem()函數将圖形項添加到場景中,然後調用任意一個圖形項發現函數來檢索添加的圖形項。
  • QGraphicsScene::items()函數和其他幾個重載函數可以傳回符合條件的所有圖形項。這些圖形項不是與指定的點、矩形、多邊形或者矢量路徑相交,就是包含在它們之中。
  • QGraphicsScene::itemAt()函數傳回指定點的最上面的圖形項。所有的圖形項發現函數傳回的圖形項都是使用遞減順序(例如第一個傳回的圖形項在最上面,最後傳回的圖形項在最下面)。
  • 如果要從場景中删除一個圖形項,可以使用QGraphicsScene::Removeltem()函數。
  • 可以通過向QGraphicsScene::setSelectionArea()函數中傳遞一個任意的形狀來選擇場景中指定的圖形項。
  • 如果要擷取目前選取的所有圖形項的清單,可以使用QGraphicsScene:: selectedltems()函數。
  • 另外可以調用QGraphicsScene:: setFocusItem()或者 QGraph­icsScene:: setFocus( )函數來為一個圖形項設定焦點,調用QGraphicsScene:: focusItem()函數來擷取目前獲得焦點的圖形項。
  • QGraphicsScene:: render()函數将場景中的一部分渲染到一個繪圖裝置上。

下面先來看一個最簡單的例子。建立空的Qt項目(Empty qmake Project),項目名稱為myscene。然後在這個項目中添加新的C++源檔案,命名為main.cpp。添加完成後首先在myscene.pro檔案中添加一行代碼:

QT += widgets
           

然後将main.cpp的内容更改如下。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QDebug>

int main(int argc,char* argv[ ])
{
    QApplication app(argc,argv);

    //建立場景
    QGraphicsScene scene;
    //建立矩形圖形項
    QGraphicsRectItem *item = new QGraphicsRectItem(0, 0, 100, 100);
    //将圖形項添加到場景中
    scene.addItem(item);
    //輸出(50, 50)點處的圖形項
    qDebug() << scene.itemAt(50,50,QTransform());

    return app.exec();
}
           

這裡先建立了一個場景,然後建立了一個矩形圖形項,并且将該圖形項添加到了場景中。然後使用itemAt()函數來傳回指定坐标處最頂層的圖形項,這裡傳回的就是剛才添加的矩形圖形項。現在可以運作程式,不過因為還沒有設定視圖,是以不會出現任何圖形界面,這時可以在應用程式輸出欄中看到輸出的項目的資訊如下:

QGraphicsItem(0x161015c8, pos=0,0)
           

1.2 視圖(View)

QGraphicsView提供了視圖部件,它用來使場景中的内容可視化。可以連接配接多個視圖到同一個場景來為相同的資料集提供多個視口。

下面是一些QGraphicsView:的常用函數:

  • 視圖部件是一個可滾動的區域,提供了一個滾動條來浏覽大的場景,可以使用setDragMode()函數以QGraphicsView::SCrollHandDrag為參數來使光标變為手掌形狀,進而可以拖動場景。
  • 如果設定 setDragMode()的參數為QGraphicsView::RubberBandDrag,那麼可以在視圖上使用 滑鼠拖出橡皮筋框來選擇圖形項。
  • 預設的QGraphicsView提供了一個QWidget作為視口部件,如果要使用OpenGL進行植染,可以調用QGraphicsView::setViewport()設定QOpenGLWidget作為視口。QGraphicsView會擷取視口部件的擁有權(ownership)。

在前面的程式中先添加頭檔案# include <QGraphicsView>,然後在主函數中 “return app. exec();”一行代碼前繼續添加如下代碼:

//為場景建立視圖
QGraphicsView view(&scene);
//設定場景的前景色
view.setForegroundBrush(QColor(255, 255, 0, 100));
//設定場景的背景圖檔
view.setBackgroundBrush(QPixmap("../myScene/background.png"));
view.resize(400, 300);
view.show();
           

這裡建立了視圖部件,并指定了要可視化的場景。然後為該視圖設定了場景前景色和背景圖檔。一個場景分為3層:圖形項層(ItemLayer)、前景層(ForegroundLayer)和背景層(BackgroundLayer)。場景的繪制總是從背景層開始,然後是圖形項層,最後是前景層。前景層和背景層都可以使用QBrush進行填充,比如使用漸變和貼圖等。這裡的前景色設定為半透明的黃色,當然也可以設定為其他的填充。這裡要提示一下,其實使用好前景色可以實作很多特殊的效果,比如使用半透明的黑色便可以實作夜幕降臨的效果。

代碼中使用了 QGraphicsView類中的函數來設定場景中的背景和前景,其實也可以使用QGraphicsScene中的同名函數來實作,不過它們的效果并不完全 一樣。如果使用QGraphicsScene對象設定了場景背景或者前景,那麼對所有關聯了該場景的視圖都有效,而QGraphicsView對象設定的場景的背景或者前景,隻對它本身對應的視圖有效。

運作程式,效果如下圖所示。可以看到矩形圖形項和背景圖檔都是在視圖中間部分進行繪制的,這個問題會在後面的坐标系統部分詳細講解。

Qt 2D繪圖:圖形視圖架構的結構和坐标系統

1.3 圖形項

QGraphicsItem是場景中圖形項的基類。圖形視圖架構為典型的形狀提供了标準的圖形項,比如矩形(QGraphicsRectlem)、橢圓(QGraphicsEllipseltem)和文本項(QGraphicsTextltem)。不過,隻有編寫自定義的圖形項時才能發揮QGraphicsItem的強大功能。

QGraphicsItem主要支援以下功能:

  • 滑鼠按下、移動、釋放、輕按兩下、懸停、滾輪和右鍵菜單事件;
  • 鍵盤輸入焦點和鍵盤事件;
  • 拖放事件;
  • 分組,使用QGraphicsItemGroup通過parent-child關系來實作;
  • 碰撞檢測。

除此之外,圖形項還可以存儲自定義的資料,可以使用setData()進行資料存儲,然後使用data()擷取其中的資料。下面自定義圖形項。

在前面的程式中添加新檔案,模闆選擇C+ +類,類名為Myltem,基類為 QGraphicsItem,類型資訊選擇“無”。添加完成後,在myitem.h檔案中添加兩個函數的聲明:

#ifndef MYITEM_H
#define MYITEM_H

#include <QGraphicsItem>

class MyItem : public QGraphicsItem
{
public:
    MyItem();

    //傳回要繪制圖形項的矩形區域
    QRectF boundingRect() const;

    //用來執行實際的繪圖操作
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
               QWidget *widget);
};

#endif // MYITEM_H

           

再到myitem.cpp檔案中添加頭檔案# include <QPainter>,然後定義添加的兩個函數:

#include "myitem.h"
#include <QPainter>

MyItem::MyItem()
{
}

//傳回要繪制圖形項的矩形區域
QRectF MyItem::boundingRect() const
{
    qreal penWidth = 1;
    return QRectF(0 - penWidth / 2, 0 - penWidth / 2,
                  20 + penWidth, 20 + penWidth);
}

//用來執行實際的繪圖操作
void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                   QWidget *widget)
{
    Q_UNUSED(option); //标明該參數沒有使用
    Q_UNUSED(widget);

    painter->setBrush(Qt::red);
    painter->drawRect(0, 0, 20, 20);
}
           

要實作自定義的圖形項,那麼首先要建立一個QGraphicsItem的子類,然後重新實作它的兩個純虛公共函數:boimdingRect()和paint(),前者用來傳回要繪制圖形項的矩形區域,後者用來執行實際的繪圖操作。其中,boimdingRect()函數将圖形項的外部邊界定義為一個矩形,所有的繪圖操作都必須限制在圖形項的邊界矩形之中。而且,QGraphicsView要使用這個矩形來剔除那些不可見的圖形項,另外QGraphicsItem的碰撞檢測機制也需要使用到這個邊界矩形。

下面到main.cpp中添加#include "myitem.h",然後将以前那個圖形項的定義語句改為:

MyItem *item = new MyItem;
           

這時運作程式,效果如下圖所示。可以看到,自定義的紅色小方塊出現在了視圖的正中間,背景圖檔的位置也有所變化,這些問題都會在後面的坐标系統中講到。如果隻想添加簡單的圖形項,那麼也可以直接使用圖形視圖架構提供的8種标準圖形項。

Qt 2D繪圖:圖形視圖架構的結構和坐标系統

下面是圖形視圖架構的事件傳遞示意圖:

Qt 2D繪圖:圖形視圖架構的結構和坐标系統

二、圖形視圖架構的坐标系統

圖形視圖架構是基于笛卡爾坐标系統的,一個圖形項在場景中的位置和幾何形狀由x坐标和y坐标來表示。當使用一個沒有變換的視圖來觀察場景時,場景中的一個單元代表螢幕上的一個像素。在圖形視圖架構中有3個有效的坐标系統:圖形項坐标、場景坐标和視圖坐标。為了友善應用,圖形視圖架構中提供了一些便捷函數來完成3個坐标系統之間的映射。當進行繪圖時,場景坐标對應QPainter的邏輯坐标,視圖坐标對應裝置坐标。

2.1 場景坐标

場景坐标是所有圖形項的基礎坐标系統。場景坐标系統描述了每一個頂層圖形項的位置,也形成了所有從視圖傳到場景上的事件的基礎。場景坐标的原點在場景的中心,x軸正方向向右,y軸正方向向下。

每一個在場景中的圖形項除了擁有一個圖形項的本地坐标和邊界矩形外,還都擁有一個場景坐标(QGraphicsItem: :scenePos())和一個場景中的邊界矩形(QGraphicsItem::sceneBoundingRect())。場景坐标用來描述圖形項在場景坐标系統中的位置,而圖形項的場景邊界矩形用于QGraphicsScene判斷場景中的哪些區域進行了更改。

QGraphicsScene類的坐标系以中心為原點(0,0),如下圖所示。

Qt 2D繪圖:圖形視圖架構的結構和坐标系統

2.2 視圖坐标

視圖的坐标就是視窗部件的坐标。視圖坐标的每一個機關對應一個像素。QGraphicsView視圖的左上角是(0,0),x軸正方向向右,y軸正方向向下。

所有的滑鼠事件最開始都是使用視圖坐标。 QGraphicsView類繼承自QWidget類,是以它與其他的QWidget類一樣,以視窗的左上角作為自己坐标系的原點,如圖所示。

Qt 2D繪圖:圖形視圖架構的結構和坐标系統

2.3 圖形項坐标

圖形項使用自己的本地坐标,這個坐标系統通常以圖形項中心為原點,這也是所有變換的原點。圖形項坐标方向是x軸正方向向右,y軸正方向向下。建立圖形項後,隻需注意圖形項坐标就可以了,QGraphicsScene和QGraphicsView會完成所有的變換。

QGraphicsItem類的坐标系,若在調用QGraphicsItem類的paint()函數重繪圖元時,則以此坐标系為基準,如下圖所示。

Qt 2D繪圖:圖形視圖架構的結構和坐标系統

2.4 坐标映射

當處理場景中的圖形項時,将坐标或者一個任意的形狀從場景映射到圖形項、或者從一個圖形項映射到另一個圖形項、或者從視圖映射到場景,這些坐标變換都是非常有用的。例如:

  • 當在QGraphicsView的視口上單擊了滑鼠,便可以調用QGraphics­View::mapToScene()以及 QGraphicsScene::itemAt()來擷取光标下的圖形項;
  • 如果要擷取一個圖形項在視口中的位置,那麼可以先在圖形項上調用QGraphicsItem::mapToScene(),然後在視圖上調用QGraphicsView: :mapFromScene();如果要擷取在視圖的一個橢圓形中包含的圖形項,可以先傳遞一個QPainterPath對象作為參數給mapToScene()函數,然後傳遞映射後的路徑給QGraphicsScene::items()函數。

不僅可以在視圖、場景和圖形項之間使用坐标映射,還可以在子圖形項和父圖形項或者圖形項和圖形項之間進行坐标映射 。圖形視圖架構提供的所有映射函數如下表所列,所有的映射函數都可以映射點、矩形、多邊形和路徑。

映 射 函 數 轉 換 類 型
QGraphicsView::mapToScene() 視圖到場景
QGraphicsView::mapFromScene() 場景到視圖
QGraphicsItem:: mapFromScene() 場景到圖形項
QGraphicsItem:: mapToScene() 圖形項到場景
QGraphicsItem:: mapToParent() 子圖形項到父圖形項
QGraphicsItem:: mapFromParent() 父圖形項到子圖形項
QGraphicsItem:: mapToItem() 本圖形項到其他圖形項
QGraphicsItem:: mapFromItem() 其他圖形項到本圖形項

文章轉自部落格園(fengMisaka):Qt 2D繪圖之五:圖形視圖架構的結構和坐标系統 - fengMisaka - 部落格園

Qt開發必備技術棧學習路線和資料

繼續閱讀