一、圖形視圖框架的結構
在前面講的基本繪圖中,我們可以自己繪制各種圖形,並且控制它們。但是,如果需要同時繪制很多個相同或不同的圖形,並且要控制它們的移動、檢測它們的碰撞和疊加;或者我們想讓自己繪制的圖形可以拖動位置、進行縮放和旋轉等操作。實現這些功能,要是還使用以前的方法,那么會十分困難。解決這些問題,可以使用Qt提供的圖形視圖框架。
圖形視圖(Graphics View)框架結構的主要特點如下:
- 圖形視圖(Graphics View)可以對大量定制的2D圖形項進行管理和相互作用。視圖部件可以讓所有圖形項可視化,它還提供了縮放和旋轉功能。
- 框架中包含了一個事件傳播構架,提供了和場景中的圖形項進行精確的雙精度交互的能力,圖形項可以處理鍵盤事件,鼠標的按下、移動、釋放和雙擊事件,還可以跟蹤鼠標的移動。
- 圖形視圖框架使用一個BSP(Binary Space Partitioning)樹來快速發現圖形項,也正是因為如此,它可以實時顯示一個巨大的場景,甚至包含上百萬個圖形項。
- 圖形視圖框架結構中,系統可以利用Qt繪圖系統的反鋸齒、OpenGL工具來改善繪圖性能。
圖形視圖結構主要包含三部分:
- 場景(Scene) :QGraphicsScene類
- 視圖(View) :QGraphicsView類
- 圖形項(Item):QGraphicsItem類
1.1 場景(Scene)
場景是圖形項QGraphicsItem對象的容器,其主要完成的工作包括: (1)提供用於管理大量圖形項的快速接口; (2)傳播事件給每一個圖形項; (3)管理圖形項的狀態(如選擇和焦點處理); (4)提供無變換的渲染功能,主要用於打印。 |
下面是一些QGraphicsScene的常用函數:
- 可以調用QGraphicsScene: :addItem()函數將圖形項添加到場景中,然后調用任意一個圖形項發現函數來檢索添加的圖形項。
- QGraphicsScene::items()函數和其他幾個重載函數可以返回符合條件的所有圖形項。這些圖形項不是與指定的點、矩形、多邊形或者矢量路徑相交,就是包含在它們之中。
- QGraphicsScene::itemAt()函數返回指定點的最上面的圖形項。所有的圖形項發現函數返回的圖形項都是使用遞減順序(例如第一個返回的圖形項在最上面,最后返回的圖形項在最下面)。
- 如果要從場景中刪除一個圖形項,可以使用QGraphicsScene::Removeltem()函數。
- 可以通過向QGraphicsScene::setSelectionArea()函數中傳遞一個任意的形狀來選擇場景中指定的圖形項。
- 如果要獲取當前選取的所有圖形項的列表,可以使用QGraphicsScene:: selectedltems()函數。
- 另外可以調用QGraphicsScene:: setFocusItem()或者 QGraphicsScene:: 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對象設置的場景的背景或者前景,只對它本身對應的視圖有效。
運行程序,效果如下圖所示。可以看到矩形圖形項和背景圖片都是在視圖中間部分進行繪制的,這個問題會在后面的坐標系統部分詳細講解。
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種標准圖形項。
下面是圖形視圖框架的事件傳遞示意圖:
二、圖形視圖框架的坐標系統
圖形視圖框架是基於笛卡爾坐標系統的,一個圖形項在場景中的位置和幾何形狀由x坐標和y坐標來表示。當使用一個沒有變換的視圖來觀察場景時,場景中的一個單元代表屏幕上的一個像素。在圖形視圖框架中有3個有效的坐標系統:圖形項坐標、場景坐標和視圖坐標。為了方便應用,圖形視圖框架中提供了一些便捷函數來完成3個坐標系統之間的映射。當進行繪圖時,場景坐標對應QPainter的邏輯坐標,視圖坐標對應設備坐標。
2.1 場景坐標
場景坐標是所有圖形項的基礎坐標系統。場景坐標系統描述了每一個頂層圖形項的位置,也形成了所有從視圖傳到場景上的事件的基礎。場景坐標的原點在場景的中心,x軸正方向向右,y軸正方向向下。 |
每一個在場景中的圖形項除了擁有一個圖形項的本地坐標和邊界矩形外,還都擁有一個場景坐標(QGraphicsItem: :scenePos())和一個場景中的邊界矩形(QGraphicsItem::sceneBoundingRect())。場景坐標用來描述圖形項在場景坐標系統中的位置,而圖形項的場景邊界矩形用於QGraphicsScene判斷場景中的哪些區域進行了更改。
QGraphicsScene類的坐標系以中心為原點(0,0),如下圖所示。
2.2 視圖坐標
視圖的坐標就是窗口部件的坐標。視圖坐標的每一個單位對應一個像素。QGraphicsView視圖的左上角是(0,0),x軸正方向向右,y軸正方向向下。 |
所有的鼠標事件最開始都是使用視圖坐標。 QGraphicsView類繼承自QWidget類,因此它與其他的QWidget類一樣,以窗口的左上角作為自己坐標系的原點,如圖所示。
2.3 圖形項坐標
圖形項使用自己的本地坐標,這個坐標系統通常以圖形項中心為原點,這也是所有變換的原點。圖形項坐標方向是x軸正方向向右,y軸正方向向下。創建圖形項后,只需注意圖形項坐標就可以了,QGraphicsScene和QGraphicsView會完成所有的變換。 |
QGraphicsItem類的坐標系,若在調用QGraphicsItem類的paint()函數重繪圖元時,則以此坐標系為基准,如下圖所示。
2.4 坐標映射
當處理場景中的圖形項時,將坐標或者一個任意的形狀從場景映射到圖形項、或者從一個圖形項映射到另一個圖形項、或者從視圖映射到場景,這些坐標變換都是非常有用的。例如:
- 當在QGraphicsView的視口上單擊了鼠標,便可以調用QGraphicsView::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() | 其他圖形項到本圖形項 |
參考: