天天看點

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

<a target="_blank" href="http://bbs.qter.org/forum.php?mod=viewthread&amp;tid=126">樓主</a>

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

 發表于 2013-5-4 15:43:02 | 檢視:

861| 回複: 0

圖形視圖架構(下)

版權聲明

導語

環境:windows xp + qt 4.8.4+qtcreator 2.6.2

目錄

三、場景(qgraphicsscene)

(一)場景層

(二)索引算法

(三)邊界矩形

(四)圖形項查找

(五)事件處理和傳播

(六)列印

四、視圖(qgraphicsview)

(一)縮放與旋轉

(二)場景邊框與場景對齊方式

(三)拖動模式

(四)事件傳遞

(五)背景緩存

(六)opengl渲染

(七)圖形項查找與圖形項組

(八)列印

正文

qgraphicsscene提供了圖形視圖架構的場景,它有以下功能:

提供了一個管理大量圖形項的快速接口

向每個圖形項傳播事件

管理圖形項的狀态,比如選擇和焦點處理

提供無轉換的渲染功能,主要用于列印

我們建立空的qt項目,項目名稱為graphicsview03,完成後添加main.cpp檔案,更改其内容如下:

#include &lt;qtgui&gt;

int main(int argc,char* argv[ ])

{

   qapplication app(argc,argv);

   qgraphicsscene scene;

   scene.addtext("hello, world!");

   qgraphicsview view(&amp;scene);

   view.show();

   return app.exec();

}

運作程式,效果如下:

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

這裡使用addtext()函數添加了一個文本圖形項。執行這條語句就相當于執行了下面兩條語句:

qgraphicstextitem*item = new qgraphicstextitem("hello, world!");

scene.additem(item);

如果要删除一個圖形項我們可以調用removeitem()函數,如:scene.removeitem(item);

一個場景分為三個層:圖形項層(itemlayer)、前景層(foregroundlayer)和背景層(backgroundlayer)。場景的繪制總是從背景層開始,然後是圖形項層,最後是前景層。看下面的例子:

我們在上面的程式中添加代碼:

scene.setforegroundbrush(qcolor(255,255,255,100));

scene.setbackgroundbrush(qt::green);

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

對于前景層,我們一般不進行設定,或者像上面這樣設定為半透明的白色。對于背景層,這裡設定為了綠色,當然,我們也可以将一張圖檔設定為背景。

scene.setbackgroundbrush(qpixmap("../graphicsview03/yafeilinux.jpg"));

運作程式,我們可以看到,圖檔預設是平鋪的。

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

如果想進一步控制前景和背景層,我們可以重新實作drawforeground()函數和drawbackground()函數。

索引算法,是指在場景中進行圖形項查找的算法。qgraphicsscene中提供了兩種選擇,它們在一個枚舉變量qgraphicsscene::itemindexmethod中,分别是:

qgraphicssecne::bsptreeindex :應用binary space partition tree,适合于大量的靜态圖形項。這個是預設值。

qgraphicsscene::noindex :不用索引,搜尋場景中所有的圖形項,适合于經常進行圖形項的添加、移動和删除等操作的情況。

我們可以使用setitemindexmethod()函數進行索引算法的更改。

圖形項可以放到場景的任何位置,場景的大小預設是沒有限制的。而場景的邊界矩形僅用于場景内部進行索引的維護。因為如果沒有邊界矩形,場景就要搜尋所有的圖形項,然後确定出其邊界,這是十分費時的。是以如果要操作一個較大的場景,我們應該給出它的邊界矩形。設定邊界矩形,可以使用setscenerect()函數。

場景最大的優勢之一就是可以快速的鎖定圖形項的位置,即使有上百萬個圖形項,items()函數也能在數毫秒的時間内鎖定一個圖形項的位置。items()函數有幾個重載函數來友善的進行圖形項的查找。但是有時在場景的一個點可能重疊着幾個圖形項,這時我們可以使用itemat()函數傳回最上面的一個圖形項。對于這些函數的使用,我們到後面講視圖時再舉例講解。

場景可以傳播來自視圖的事件,将事件傳播給該點最頂層的圖形項。但是就像我們在講圖形項時所說的那樣,如果一個圖形項要接收鍵盤事件,那麼它必須獲得焦點。而且,如果我們在場景中重寫了事件處理函數,那麼在該函數的最後,必須調用場景預設的事件處理函數,隻有這樣,圖形項才能接收到該事件。這一點我們也到後面講視圖時再細講。

該部分内容也放到後面和視圖一起講。

qgraphicsview 提供了視圖視窗部件,它使場景的内容可視化。你可以給一個場景關聯多個視圖,進而給一個資料集提供多個視口。視圖部件是一個滾動區域,就是說,它可以提供一個滾動條來顯示大型的場景。如果要使用opengl,你可以使用qgraphicsview::setviewport()函數來添加qglwidget 。

我們建立空的qt項目,項目名稱為graphicsview04,然後添加main.cpp檔案,再新添一個c++ 類,類名為myview,基類為qgraphicsview,類型資訊選擇“繼承自qwidget”。

然後在myview.h中添加頭檔案:#include &lt;qtgui&gt;

然後聲明事件槽函數:

protected:

   void wheelevent(qwheelevent *event);

voidmousepressevent(qmouseevent *event);

我們到myview.cpp檔案中進行函數的定義:

myview::myview(qwidget *parent) :

   qgraphicsview(parent)

   resize(400,400);

    setbackgroundbrush(qpixmap("../graphicsview04/01.jpg"));//其實就是設定場景的背景

   qgraphicsscene *scene = new qgraphicsscene(this);

   scene-&gt;setscenerect(0,0,200,200);

   qgraphicsrectitem *item = new qgraphicsrectitem(0,0,20,20);

   item-&gt;setbrush(qt::red);

   scene-&gt;additem(item);

   setscene(scene);

void myview::wheelevent(qwheelevent*event)  //滾輪事件

   if(event-&gt;delta() &gt; 0)  //如果滑鼠滾輪遠離使用者,則delta()傳回正值

       scale(0.5,0.5);  //視圖縮放

   else scale(2,2);

void myview::mousepressevent(qmouseevent*event)

   rotate(90);  //視圖旋轉順時針90度

這裡我們定義了滑鼠的滾輪事件和按下事件,在滾輪事件中,利用delta()函數傳回值的正負來判斷滾輪的移動方向,然後我們讓視圖進行縮放。

最後到main.cpp檔案中,更改其内容如下:

#include "myview.h"

int main(int argc,char *argv[])

   myview *view = new myview;

   view-&gt;show();

我們運作程式,效果如下:

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)
[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)
[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)
[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

上面四幅圖分别是:正常,旋轉90度後,縮小後,放大後的效果。可以看到實作視圖的變換是十分簡單的。

我們在上面講場景時就提到了場景邊框(scenerect),這裡再說說它在視圖中的作用。我們前面說過,視圖是可以提供滾動條的,但是,這隻是在視圖視窗小于場景時才自動出現的。如果我們不定義場景邊框,那麼當場景中的圖形項移動到視圖可視視窗以外的地方時,視圖就會自動出現滾動條,但是即使是圖形項再次回到可視區域,滾動條也不會消失。為了解決這個問題,我們可以為場景設定邊框,這樣,當圖形項移動到場景邊框以外時,視圖是不會提供額外的滾動區域的。

       而當整個場景都可視時,也就是說視圖沒有滾動條時,我們可以通過setalignment()函數來設定場景在視圖中的對齊方式,如左對齊qt::alignleft ,向上對齊qt::aligntop ,中心對齊qt::aligncenter。更多的對齊方式,可以檢視幫助中qt::alignment 關鍵字。預設的對齊方式是qt::aligncenter 。而且幾種對齊方式可以通過“按位或”操作一起使用。我們在上面的程式中的myitem.cpp檔案中的構造函數最後添加一行代碼:

setalignment(qt::alignleft | qt::aligntop);

運作效果如下圖所示。

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

在qgraphicview中提供了三種拖動模式,分别是:

qgraphicsview::nodrag :忽略滑鼠事件,不可以拖動。

qgraphicsview::scrollhanddrag :光标變為手型,可以拖動場景進行移動。

qgraphicsview::rubberbanddrag :使用橡皮筋效果,進行區域選擇,可以選中一個區域内的所有圖形項。

我們可以利用setdragmode()函數進行相應設定。

下面我們更改上面的程式。在myview.cpp中的構造函數中的最後添加一行代碼:

setdragmode(qgraphicsview::scrollhanddrag);//手型拖動

并将場景外框放大一點:

scene-&gt;setscenerect(0,0,800,800);

這時運作程式,雖然出現了小手,但是并不能拖動場景。為什麼呢?我們在mousepressevent()函數中添加一行代碼:

qgraphicsview::mousepressevent(event);

這時再運作程式,發現已經成功了。效果如下:

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

我們在事件函數的最後添加了一行:qgraphicsview::mousepressevent(event);這樣程式才能執行預設的事件。這也是我們下面要說的事件傳播的内容的一部分。

在上面我們看到必須在事件函數的最後将event參數傳遞出去,才能執行預設的事件操作。其實不止上面那一種情況,在圖形視圖架構中,滑鼠鍵盤等事件是從視圖進入的,視圖将它們傳遞給場景,場景再将事件傳遞給該點的圖形項,如果該點有多個圖形項,那麼就傳給最上面的圖形項。是以要想使這個事件能一直傳播下去,我們就需要在重新實作事件處理函數時,在其最後将event參數傳給預設的事件處理函數。比如我們重寫了場景的鍵盤按下事件處理函數,那麼我們就在該函數的最後寫上qgraphicsscene::keypressevent(event);一行代碼。

如果場景的背景需要大量耗時的渲染,可以利用cachebackground來緩存背景,當下次需要渲染背景時,可以快速進行渲染。它的原理就是,把整個視口先繪制到一個pixmap上。但是這個隻适合較小的視口,也就是說,如果視圖視窗很大,而且有滾動條,那麼就不再适合緩存背景。我們可以使用setcachemode(qgraphicsview::cachebackground);來設定背景緩存。預設設定是沒有緩存qgraphicsview::cachenone。

qgraphicsview預設使用一個qwidget作為視口部件,如果我們要使用opengl進行渲染,可以使用setviewport()函數來添加一個qglwidget對象。看下面的例子。

我們先在項目檔案graphicsview04.pro中加入

qt += opengl

說明要使用opengl子產品,然後在myview.cpp檔案中添加頭檔案:

#include &lt;qtopengl&gt;

最後在構造函數中加入代碼:

qglwidget *widget =new qglwidget(this);

setviewport(widget);

這樣便使用opengl進行渲染了。關于opengl,我們在後面的3d繪圖部分再講。

在前面講場景時,我們就涉及了圖形項查找的内容,當時沒有細講,現在我們把它和圖形項組放到一起來講解。先看一個例子,然後再介紹。

在myview.cpp中的構造函數裡将以前那個item改名為item1,然後再加入一個item2和一個圖形項組對象group。更改後構造函數的部分代碼如下:

qgraphicsrectitem *item1 = newqgraphicsrectitem(0,0,20,20);

item1-&gt;setbrush(qt::red);

item1-&gt;setpos(10,0);

scene-&gt;additem(item1);

qgraphicsrectitem *item2 = newqgraphicsrectitem(0,0,20,20);

item2-&gt;setbrush(qt::green);

item2-&gt;setpos(30,0);

scene-&gt;additem(item2);

qgraphicsitemgroup *group = newqgraphicsitemgroup;  //建立圖形項組

group-&gt;addtogroup(item1);

group-&gt;addtogroup(item2);

scene-&gt;additem(group);

setscene(scene);

qdebug() &lt;&lt; "itemat(10,0) :" &lt;&lt;itemat(10,0); //輸出(10,0)點的圖形項

qdebug() &lt;&lt; "itemat(30,0) :" &lt;&lt;itemat(30,0);

qdebug() &lt;&lt;"#################################"; //分割線

然後我們到myview.h檔案中protected下聲明鍵盤按下事件槽函數:

void keypressevent(qkeyevent *event);

再到myview.cpp中定義它,如下:

void myview::keypressevent(qkeyevent*event)

   qdebug() &lt;&lt; items();  //輸出場景中所有的圖形項

   items().at(0)-&gt;setpos(100,0);

   items().at(1)-&gt;setpos(0,100);

   qgraphicsview::keypressevent(event); //執行預設的事件處理

這時運作程式,當按下鍵盤上任意鍵後,效果如下:

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

下面是輸出框輸出的資訊:

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

可以看到,itemat()函數可以輸出場景上任意點的圖形項。而items()函數可以輸出場景上所有的圖形項。這裡應該說明,items()函數傳回的圖形項清單是按棧的降序排序的,也就是說,items().at(0)傳回的是最後加入場景的圖形項。從上面可以看出,最後加入的圖形項是item2,其實,因為我們使用了group,而item1和item2都在group裡,是以我們隻需将group加入場景中就可以了,前面把item1和item2也加入場景是多餘的。我們可以将scene-&gt;additem(item1);和scene-&gt;additem(item2);兩行代碼删掉。那麼這時加入場景的順序就是,先加入group,因為item1先加入group,是以下面将item1加入場景,最後加入場景的是item2,這就是為什麼items.at(0)會是item2的原因。

       下面再說圖形項組,其實圖形項組也是一個圖形項,它有圖形項所擁有的所有特性。其作用就是,将加入它的所有圖形項作為一個整體,對這個圖形項組進行操作,就相當于對齊中所有圖形項進行操作。圖形項組是加入它的所有圖形項的父圖形項,在上面的輸出的parent資訊中我們可以看到這一點。下面我們将程式中的代碼更改如下:

   items().at(2)-&gt;setpos(100,100);

運作程式,按下鍵盤上任意鍵,效果如下:

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

可以看到,兩個圖形項是同時移動的。我們要從圖形項組中移除一個圖形項,可以使用removefromgroup()函數,它可以将給定的item從group中删除,要注意這時item依然存在,它會回到group的父圖形項中,如果group沒有父圖形項,那麼item就會回到場景中。我們可以使用場景的removeitme()函數來删除group,這樣也會将group中所有的圖形項從場景中删除。還有一種辦法是利用場景的destroyitemgroup()函數,它會删除group并銷毀它,但是group中的所有圖形項會回到group的父圖形項中,如果它沒有父圖形項,那麼所有圖形項就會回到場景中。

圖形視圖架構提供了兩個列印函數render(),一個是在qgraphicsscene中,一個是在qgraphicsview中,并且它們的函數原型是一模一樣的。不過它們實作的效果稍有不同。看一面的例子。

我們更改滑鼠按下事件槽函數的内容如下:

    rotate(90); //視圖旋轉順時針90度

   qpixmap pixmap(400,400);  //必須指定大小

   qpainter painter(&amp;pixmap);

   render(&amp;painter,qrectf(0,0,400,400),qrect(0,0,400,400));  //列印視圖指定區域内容

    pixmap.save("../graphicsview04/save.png");

   qgraphicsview::mousepressevent(event);

這裡我們使用了視圖的render()函數,其中的qrectf參數是指裝置的區域,這裡是指pixmap。而qrect參數是指視圖上要列印的區域。我們利用qpixmap類的save()函數,将pixmap圖檔儲存到我們項目源碼目錄中,檔案名為“save.png”。下面是運作程式後,點選滑鼠,生成的圖檔的效果:

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)
[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

我們每點選一次滑鼠,就會旋轉視圖,那麼生成的圖檔就是目前視口的截圖。下面我們使用場景的列印函數,将上面的列印一行的代碼改為:

scene()-&gt;render(&amp;painter,qrectf(0,0,400,400),qrect(0,0,400,400));//列印場景内容

檢視圖檔效果:

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

這時無論視圖怎樣變換,生成的圖檔總是一樣的。而且它并沒有列印場景背景的圖檔。就像我們看到的,視圖的列印函數是依據視圖的坐标系進行列印的,我們看到的就是列印出來後的效果,它可以看做是程式視窗的截屏。而場景的列印函數,是依據場景的坐标系的,無論視圖怎麼轉換,隻要場景坐标系沒有變換,它列印出來的圖檔都是一樣的。

結語

涉及到的源碼: 

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

kb, 下載下傳次數: 11) 

[Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下) [Qt教程] 第20篇 2D繪圖(十)圖形視圖架構(下)

kb, 下載下傳次數: 16) 

繼續閱讀