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

發表于 2013-5-4 15:26:20 | 檢視:
1798| 回複: 26
圖形視圖架構(上)
版權聲明
導語
在前面講的基本繪圖中,我們可以自己繪制各種圖形,并且控制它們。但是,如果需要同時繪制很多個相同或不同的圖形,并且要控制它們的移動,檢測它們的碰撞和疊加;或者我們想讓自己繪制的圖形可以拖動位置,進行縮放和旋轉等操作。實作這些功能,要是還使用以前的方法,那麼會十分困難。解決這些問題,可以使用qt提供的圖形視圖架構。
圖形視圖可以對大量定制的2d圖形項進行管理和互相作用。視圖部件可以讓所有圖形項可視化,它還提供了縮放和旋轉功能。我們在幫助中搜尋graphics
view 關鍵字,内容如下圖:
這裡一開始對這個架構進行了簡單介紹,整個圖形視圖結構主要包含三部分:場景(scene)、視圖(view)和圖形項(item),它們分别對應 qgraphicsscene 、qgraphicsview 、qgraphicsitem三個類。其實圖形視圖架構是一組類的集合,在幫助中可以看到所有與它相關的類。下面我們就開始結合程式對整個架構進行介紹。
環境:windows xp + qt 4.8.4+qtcreator 2.6.2
目錄
一、基本應用
二、圖形項(qgraphicsitem)
(一)自定義圖形項
(二)光标和提示
(三)拖放
(四)鍵盤與滑鼠事件
(五)碰撞檢測
(六)移動
(七)動畫
(八)右鍵菜單
正文
我們建立空的qt項目(在其他項目中),項目名稱為graphicsview01。然後在這個項目中添加新的c++ 源檔案,命名為main.cpp。
我們将main.cpp的内容更改如下。
#include <qtgui>
int main(int argc,char* argv[ ])
{
qapplication app(argc,argv);
qgraphicsscene *scene = new qgraphicsscene; //場景
qgraphicsrectitem *item = new qgraphicsrectitem(100,100,50,50); //矩形項
scene->additem(item); //項添加到場景
qgraphicsview *view = new qgraphicsview; //視圖
view->setscene(scene); //視圖關聯場景
view->show(); //顯示視圖
return app.exec();
}
這裡我們建立了一個最簡單的基于這個圖形視圖架構的程式。分别建立了一個場景,一個圖形項和一個視圖,并将圖形項添加到場景中,将視圖與場景關聯,最後顯示視圖就可以了。基于這個架構的所有程式都是這樣實作的。運作效果如下。
就像我們看到的,場景是管理圖形項的,所有的圖形項必須添加到一個場景中,但是場景本身無法可視化,我們要想看到場景上的内容,必須使用視圖。下面我們分别對圖形項、場景和視圖進行介紹。
qgraphicsitem類是所有圖形項的基類。圖形視圖架構對一些典型的形狀提供了一些标準的圖形項。比如上面我們使用的矩形(qgraphicsrectitem)、橢圓(qgraphicsellipseitem)、文本(qgraphicstextitem)等多個圖形項。但隻有繼承qgraphicsitem 類實作我們自定義的圖形項時,才能顯示出這個類的強大。qgraphicsitem支援以下功能:
滑鼠的按下、移動、釋放和輕按兩下事件,也支援滑鼠懸停、滾輪和右鍵菜單事件。
鍵盤輸入焦點和鍵盤事件
拖放
利用qgraphicsitemgroup進行分組
碰撞檢測
1.在前面的項目中添加新的c++類,類名設為 myitem,基類設為qgraphicsitem。
2.然後,我們在myitem.h檔案中添加頭檔案#include <qtgui>。(說明:qtgui子產品裡面包含了所有圖形界面類,是以為了簡便,這裡隻包含了該頭檔案,正式開發程式時不推薦這麼做!)
3.再添加兩個函數的聲明:
qrectfboundingrect() const;
voidpaint(qpainter *painter, const qstyleoptiongraphicsitem *option, qwidget*widget);
4.下面到myitem.cpp中對兩個函數進行定義:
qrectfmyitem::boundingrect() const
qreal penwidth = 1;
return qrectf(0 - penwidth / 2, 0 -penwidth / 2,
20 + penwidth, 20 + penwidth);
voidmyitem::paint(qpainter *painter, const qstyleoptiongraphicsitem *option,qwidget *widget)
q_unused(option); //标明該參數沒有使用
q_unused(widget);
painter->setbrush(qt::red);
painter->drawrect(0,0,20,20);
5.下面到main.cpp中添加#include "myitem.h"
然後将以前那個矩形項的定義語句改為:
myitem *item =new myitem;
運作程式,效果如下:
可以看到,我們要繼承qgraphicsitem類實作自定義的圖形項,必須先實作兩個純虛函數boundingrect()和paint(),前者用于定義item的繪制範圍,後者用于繪制圖形項。其實boundingrect()還有很多用途,後面會涉及到。
1.在myitem.cpp 中的構造函數中添加兩行代碼,如下:
myitem::myitem()
settooltip("click and drag me!"); //提示
setcursor(qt::openhandcursor); //改變光标形狀
然後運作程式,效果如下:
當光标放到小方塊上時,光标變為了手型,并且彈出了提示。更多的光标形狀可以檢視
qt::cursorshape,我們也可以使用圖檔自定義光标形狀。
下面寫這樣一個程式,有幾個不同顔色的圓形和一個大矩形,我們可以拖動圓形到矩形上,進而改變矩形的顔色為該圓形的顔色。
1. 将上面的程式進行改進,用來實作圓形圖形項。
在myitem.h中添加一個私有變量和幾個鍵盤事件處理函數的聲明:
protected:
void mousepressevent(qgraphicsscenemouseevent *event);
void mousemoveevent(qgraphicsscenemouseevent *event);
void mousereleaseevent(qgraphicsscenemouseevent *event);
private:
qcolor color;
2.然後到myitem.cpp中,在構造函數中初始化顔色變量:
color = qcolor(qrand() % 256, qrand() %256, qrand() % 256); //初始化随機顔色
在paint()函數中将繪制矩形的代碼更改如下:
painter->setbrush(color);
painter->drawellipse(0, 0, 20, 20);
3.下面我們定義幾個鍵盤事件處理函數:
voidmyitem::mousepressevent(qgraphicsscenemouseevent *event)
if(event->button() != qt::leftbutton)
{
event->ignore(); //如果不是滑鼠左鍵按下,則忽略該事件
return;
}
setcursor(qt::closedhandcursor); //如果是滑鼠左鍵按下,改變光标形狀
voidmyitem::mousemoveevent(qgraphicsscenemouseevent *event)
if(qlinef(event->screenpos(),event->buttondownscreenpos(qt::leftbutton))
.length() < qapplication::startdragdistance())
//如果滑鼠按下的點到現在的點的距離小于程式預設的拖動距離,表明沒有拖動,則傳回
qdrag *drag = new qdrag(event->widget()); //為event所在視窗部件建立拖動對象
qmimedata *mime = new qmimedata; //建立qmimedata對象,它用來存儲拖動的資料
drag->setmimedata(mime); //關聯
mime->setcolordata(color); //放入顔色資料
qpixmap pix(21,21); //建立qpixmap對象,它用來重新繪制圓形,在拖動時顯示
pix.fill(qt::white);
qpainter painter(&pix);
paint(&painter,0,0);
drag->setpixmap(pix);
drag->sethotspot(qpoint(10, 15)); //我們讓指針指向圓形的(10,15)點
drag->exec(); //開始拖動
voidmyitem::mousereleaseevent(qgraphicsscenemouseevent *event)
此時運作程式,效果如下:
4.下面我們新添一個類,它用來提供矩形圖形項,并且可以接收拖動的資料。
在myitem.h中,我們加入該類的聲明:
class rectitem : public qgraphicsitem
public:
rectitem();
qrectf boundingrect() const;
void paint(qpainter *painter, const qstyleoptiongraphicsitem *option,qwidget *widget);
void dragenterevent(qgraphicsscenedragdropevent *event); //拖動進入事件
void dragleaveevent(qgraphicsscenedragdropevent *event); //拖動離開事件
void dropevent(qgraphicsscenedragdropevent *event); //放入事件
qcolor color;
bool dragover; //标志是否有拖動進入
};
5.然後進入myitem.cpp進行相關函數的定義:
rectitem::rectitem()
setacceptdrops(true); //設定接收拖放
color = qcolor(qt::lightgray);
qrectf rectitem::boundingrect() const
return qrectf(0, 0, 50, 50);
void rectitem::paint(qpainter *painter,const qstyleoptiongraphicsitem *option, qwidget *widget)
painter->setbrush(dragover? color.light(130) : color); //如果其上有拖動,顔色變亮
painter->drawrect(0,0,50,50);
voidrectitem::dragenterevent(qgraphicsscenedragdropevent *event)
if(event->mimedata()->hascolor()) //如果拖動的資料中有顔色資料,便接收
event->setaccepted(true);
dragover = true;
update();
else event->setaccepted(false);
voidrectitem::dragleaveevent(qgraphicsscenedragdropevent *event)
q_unused(event);
dragover = false;
update();
void rectitem::dropevent(qgraphicsscenedragdropevent*event)
if(event->mimedata()->hascolor())
//我們通過類型轉換來獲得顔色
color =qvariantvalue<qcolor>(event->mimedata()->colordata());
6.下面進入main.cpp檔案,更改main()函數中的内容如下:
qsrand(qtime(0,0,0).secsto(qtime::currenttime())); //設定随機數初值
qgraphicsscene *scene = new qgraphicsscene;
for(int i=0; i<5; i++) //在不同位置建立5個圓形
myitem *item = new myitem;
item->setpos(i*50+20,100);
scene->additem(item);
rectitem *rect = new rectitem; //建立矩形
rect->setpos(100,200);
scene->additem(rect);
qgraphicsview *view = new qgraphicsview;
view->setscene(scene);
view->resize(400,300); //設定視圖大小
view->show();
這是運作程式,效果如下:
這時我們已經實作了想要的效果。可以看到,要想實作拖放,必須源圖形項和目标圖形項都進行相關設定。在源圖形項的滑鼠事件中建立并執行拖動,而在目标圖形項中必須指定setacceptdrops(true); 這個函數,這樣才能接收拖放,然後需要實作拖放的幾個事件處理函數。
1.建立項目graphicsview02,然後按照(一)中自定義圖形項進行操作(可以直接把那裡的代碼拷貝過來)。下面我們先來看鍵盤事件。
2.在myitem.h檔案中聲明鍵盤按下事件處理函數:
voidkeypressevent(qkeyevent *event);
然後在myitem.cpp中進行定義:
void myitem::keypressevent(qkeyevent*event)
moveby(0, 10); //相對現在的位置移動
這時運作程式,發現無論怎樣方塊都不會移動。其實要想使圖形項接收鍵盤事件,就必須使其可獲得焦點。我們在構造函數裡添加一行代碼:
setflag(qgraphicsitem::itemisfocusable); //圖形項可獲得焦點
(我們在建立圖形項時指定也是可以的,如item->setflag(qgraphicsitem::itemisfocusable);)
這時運作程式,然後用滑鼠點選一下方塊,再按下任意按鍵,方塊就會向下移動。效果如下圖所示。
3.再看滑鼠事件。我們先在myitem.h檔案中聲明滑鼠按下事件處理函數:
voidmousepressevent(qgraphicsscenemouseevent *event);
然後再myitem.cpp檔案中對其進行定義:
moveby(10,0);
此時運作程式,點選小方塊,它便會向右移動。如果我們想讓滑鼠可以拖動小方塊,那麼我們可以重新實作mousemoveevent()函數,還有一種更簡單的方法是,我們在構造函數中指明該圖形項是可移動的:
setflag(qgraphicsitem::itemismovable);
(當然我們也可以在建立圖形項時指定它)
下面先看一個例子,再進行講解。
我們将上面程式中myitem.cpp檔案中的paint()函數中的設定畫刷的代碼更改如下:
//如果與其他圖形項碰撞則顯示紅色,否則顯示綠色
painter->setbrush(!collidingitems().isempty()?qt::red : qt::green);
然後再main.cpp檔案中在場景中添加一個直線圖形項:
qgraphicslineitem *line = newqgraphicslineitem(0,50,300,50);
scene->additem(line);
這時運作程式,效果如下:
剛開始,方塊是綠色的,當我們拖動它與直線相交時,它就變成了紅色。
在qgraphicsitem類中有三個碰撞檢測函數,分别是collideswithitem()、collideswithpath()和collidingitems(),我們使用的是第三個。第一個是該圖形項是否與指定的圖形項碰撞,第二個是該圖形項是否與指定的路徑碰撞,第三個是傳回所有與該圖形項碰撞的圖形項的清單。在幫助中我們可以檢視它們的函數原型和介紹,這裡想說明的是,這三個函數都有一個共同的參數qt::itemselectionmode,它指明了怎樣去檢測碰撞。我們在幫助中進行檢視,可以發現它是一個枚舉變量,一共有四個值,分别是:
qt::containsitemshape:隻有圖形項的shape被完全包含時;
qt::intersectsitemshape:當圖形項的shape被完全包含時,或者圖形項與其邊界相交;
qt::containsitemboundingrect: 隻有圖形項的bounding rectangle被完全包含時;
qt::intersectsitemboundingrect:圖形項的boundingrectangle被完全包含時,或者圖形項與其邊界相交。
如果我們不設定該參數,那麼他預設使用qt::intersectsitemshape 。這裡所說的shape是指什麼呢?在qgraphicsitem類中我們可以找到shape()函數,它傳回的是一個qpainterpath對象,也就是說它能确定我們圖形項的形狀。但是預設的,它隻是傳回boundingrect()函數傳回的矩形的形狀。下面我們具體驗證一下。
在main.cpp函數中添加兩行代碼:
qdebug()<< item->shape(); //輸出item的shape資訊
qdebug()<< item->boundingrect(); //輸出item的boundingrect資訊
這時運作程式,在下面的程式輸出視窗會輸出如下資訊:
我們發現,現在shape和boundingrect的大小是一樣的。這時我們在到myitem.cpp中更改函數boundingrect()函數中的内容,将大小由20,改為50:
return qrectf(0 - penwidth / 2, 0 -penwidth / 2,
50 + penwidth, 50 + penwidth);
這時再次運作程式,效果如下:
小方塊一出來便成為了紅色,下面的輸出資訊也顯示了,現在shape的大小也變成了50。怎樣才能使小方塊按照它本身的形狀,而不是其boundingrect的大小來進行碰撞檢測呢?我們需要重新實作shape()函數。
在myitem.h中,我們在public裡進行函數聲明:qpainterpath
shape() const;
然後到myitem.cpp中進行其定義:
qpainterpath myitem::shape() const
qpainterpath path;
path.addrect(0,0,20,20); //圖形項的真實大小
return path;
這時我們再運作程式,效果如下:
可以看到,現在shape和boundingrect的大小已經不同了。是以對于不是矩形的形狀,我們都可以利用shape()函數來傳回它的真實形狀。
對于圖形項的移動,我們有很多辦法實作,也可以在很多層面上對其進行控制,比如說在view上控制或者在scene上控制。但是對于大量的不同類型的圖形項,怎樣能一起控制呢?在圖形視圖架構中提供了advance()槽函數,這個函數在qgraphicsscene和qgraphicsitem中都有,在圖形項類中它的原型是advance(int
phase)。它的實作流程是,我們利用qgraphicsscene類的對象調用qgraphicsscene的advance()函數,這時就會執行兩次該場景中所有圖形項的advance(int
phase)函數,第一次phase為0,告訴所有圖形項即将要移動;第二次phase的值為1,這時執行移動。下面我們看一個例子。
我們在myitem.h中的protected中聲明函數:void advance(int
phase);
然後在myitem.cpp中對其進行定義:
void myitem::advance(int phase)
if(!phase) return; //如果phase為0,則傳回
moveby(0,10);
在到main.cpp中添加以下代碼:
qtimer timer;
qobject::connect(&timer, signal(timeout()),scene, slot(advance()));
timer.start(1000);
這時運作程式,小方塊就會每秒下移一下。
其實實作圖形項的動畫效果,也可以在不同的層面進行。如果我們隻想控制一兩個圖形項的動畫,一般在場景或視圖中實作。但是要是想讓一個圖形項類的多個對象都進行同樣的動畫,那麼我們就可以在圖形項類中進行實作。我們先看一個例子。
在myitem.cpp檔案中的構造函數中添加代碼:
setflag(qgraphicsitem::itemisfocusable); //圖形項可獲得焦點
setflag(qgraphicsitem::itemismovable); //圖形項可移動
qgraphicsitemanimation *anim = new qgraphicsitemanimation; //建立動畫類對象
anim->setitem(this); //将該圖形項加入動畫類對象中
qtimeline *timeline = new qtimeline(1000); //建立長為1秒的時間線
timeline->setloopcount(0); //動畫循環次數為0,表示無限循環
anim->settimeline(timeline); //将時間線加入動畫類對象中
anim->setrotationat(0.5,180); //在動畫時間的一半時圖形項旋轉180度
anim->setrotationat(1,360); //在動畫執行完時圖形項旋轉360度
timeline->start(); //開始動畫
這時執行程式,效果如下:
小方塊會在一秒内旋轉一圈。我們這裡使用了qgraphicsitemanimation動畫類和qtimeline時間線類,關于這些内容我們會在後面的動畫架構中細講,是以在這裡就不再介紹。
圖形項支援右鍵菜單,不過qgraphicsitem類并不是從qobject類繼承而來的,是以它預設不能使用信号和槽機制,為了能使用信号和槽,我們需要将我們的myitem類進行多重繼承。
在myitem.h中,将myitem類改為
class myitem : public qobject, publicqgraphicsitem
q_object //進行宏定義
… …
這樣我們就可以使用信号和槽機制了,這時我們在下面添加一個槽:
public slots:
void moveto(){setpos(0,0);}
因為其實作的功能很簡單,我們在聲明它的同時進行了定義,就是讓圖形項移動到(0,0)點。
然後我們在protected中聲明右鍵菜單事件處理函數:
voidcontextmenuevent(qgraphicsscenecontextmenuevent *event);
最後我們在myitem.cpp檔案中對該事件處理函數進行定義:
voidmyitem::contextmenuevent(qgraphicsscenecontextmenuevent *event)
qmenu menu;
qaction *action = menu.addaction("moveto(0,0)");
connect(action,signal(triggered()),this,slot(moveto()));
menu.exec(event->screenpos()); //在按下滑鼠左鍵的地方彈出菜單
這裡我們隻是設定了一個菜單,當按下該菜單是,圖形項移動到(0,0)點。
我們運作程式,效果如下:
結語
這一節先介紹了圖形項的相關内容,而場景、視圖等内容放到下一節來講。
涉及到的源碼:
kb, 下載下傳次數: 30)
kb, 下載下傳次數: 23)