Qt Graphics View Framework 中的事件

延續之前的和《建立自己的 QGraphicsItem》,在建立出自己的 Graphics Item 後,接下來來研究一下在 Graphics View 這個架構下的事件處理吧∼基本上,在《Qt Graphics View Framework 簡介》一文中已經有很簡單地提到了,Qt 在 Graphics View 這個 Framework 下,處理事件的方式,是會由 View 來做最外層的接收、然後傳遞給 Scene,再視需要傳遞給個別的 Item。而要把事件處理的程式碼寫在哪一層呢?這就是要看自己的需求了∼

而 View、Scene、Item 這三種類型的物件由於本身的性質、功能就不一樣,所以能支援的 event 也不完全相同;在這邊就不詳列了,請自行參考官方文件(QGraphicsViewQGraphicsSceneQGraphicsItem)。

接下來,Heresy 姑且就以這三者都有的事件:滑鼠雙擊(Double Click、滑鼠左鍵連點兩下)作範例,來大概寫個範例程式吧∼

Item 中的事件

而首先,在 item 的部分,這邊主要是沿用《建立自己的 QGraphicsItem》所建立的 CItem;不過,在這邊為了要處理 Double Click 的事件,所以還要再加上 mouseDoubleClickEvent() 這個函式。

class CItem : public QGraphicsItem
{
public:
CItem() {...}
QRectF boundingRect() const {...}
QPainterPath shape() const {...}
void paint( QPainter *painter,
const QStyleOptionGraphicsItem *option, QWidget *widget )
{...}

void mouseDoubleClickEvent( QGraphicsSceneMouseEvent* mouseEvent  )
{
QPointF pos = mouseEvent->pos();
cout << "Click on item: (" << pos.x() << "," << pos.y() << ")" << endl;
}

protected:
QBrush m_qBrush;
QPainterPath m_qPath;
};

CItemmouseDoubleClickEvent() 這個函式,他會接到 Qt 系統傳進來的參數 mouseEvent;這個變數的型別是 graphics view 架構下特有的事件物件 QGraphicsSceneMouseEvent。在這個事件物件裡,他會記錄按鈕的資訊、以及在 Scene 架構下觸發事件時的滑鼠位置(包括在不同座標系統的位置)等等的資訊,讓程式可以根據這些資訊,來做對應的處理。

而在上面的範例裡,Heresy 就是簡單地透過 QGraphicsSceneMouseEvent::pos() 這個函式,來取得滑鼠在物件裡的位置(item 自身的座標系統),並輸出。而這也就是 QGraphicsItem 一般事件處理的寫法了∼如果要針對 item 本身作一些改變、或是觸發新的 signal 也都是可以了。

另外要注意的是,他判斷是否要觸發 item 事件的滑鼠位置判斷方式、基本上應該是根據輪廓函式 shape() 所回傳的結果,如果沒有實作這個函式的話,則是會改用 boundingRect() 這個較粗糙的結果;有興趣的話,可以試著把 shape() 這個函式拿掉,你會發現可以點到這個物件的範圍會變大。

 

Scene 中的事件

而如果是在 scene 的層級要做這件事的話,則是要自己寫一個繼承 QGraphicsScene  的新 Scene 出來;而和 QGraphicsItem 不同,繼承自 QGraphicsScene 的新類別沒有任何「必要」的函式,只要看自己的需求、去重新實作出新的、對應的事件處理函式就可以了。

下面就是一個只有重新定義 mouseDoubleClickEvent() 這個函式的 scene:

class CScene : public QGraphicsScene
{
protected:
void mouseDoubleClickEvent( QGraphicsSceneMouseEvent* mouseEvent  )  {
QPointF pos = mouseEvent->scenePos();
cout << "Click on scene: (" << pos.x() << "," << pos.y() << ")" << endl;

QList<QGraphicsItem*> aItem = QGraphicsScene::items( pos );
cout << " There are " << aItem.size() << " items." << endl;
QGraphicsScene::mouseDoubleClickEvent( mouseEvent );
}
};

QGraphicsScenemouseDoubleClickEvent() 這個函式和 QGraphicsItem 的一樣,都是接受一個型別是 QGraphicsSceneMouseEvent 的物件來當作變數。

而 Heresy 在這邊,是先透過 scenePos() 這個函式,來取得滑鼠在 scene 裡面的座標位置(scene 的座標系統)、並輸出;而之後,則是再透過 QGraphicsScene::items() 這個函式,取得在這個位置的所有 item;而他會以 QList 的形式回傳,所以也可以很簡單地取出個別 item。

最後要注意的是,這邊還要再去呼叫 QGraphicsScene 原來的 mouseDoubleClickEvent() 函式,讓 QGraphicsScene 還是可以在必要的時候、把 event 繼續傳遞給 scene 裡面的個別 item;如果沒有加上這一行的話,item 的 mouseDoubleClickEvent() 是不會被觸發的。

 

View 中的事件

而在 view 的部分,也是要繼承原有的 QGraphicsView,來重新定義一個自己的 view;而和 QGraphicsScene 一樣,他沒有一定得要重新實作的函式,只要視自己的需求去重新實作事件函式就可以了。

這邊 Heresy 一樣是只有去重新實作 mouseDoubleClickEvent() 這個函式:

class CGView : public QGraphicsView
{
protected:
void mouseDoubleClickEvent( QMouseEvent* mouseEvent  )  {
QPoint pos = mouseEvent->pos();
cout << "Click on view: (" << pos.x() << "," << pos.y() << ")" << endl;

QGraphicsView::mouseDoubleClickEvent( mouseEvent );

QGraphicsItem* pItem = QGraphicsView::itemAt( pos );
if( pItem != NULL )
QGraphicsView::centerOn( pItem );
}
};

而和 QGraphicsItem 以及 QGraphicsScene 兩者不同,QGraphicsViewmouseDoubleClickEvent() 這個函式並不是在 scene 架構內的事件處理函式,他得到的參數是型別為 QMouseEvent 的變數;這是由於在 Qt Graphics View 的架構下,view 是對外的元件,他是要去處理是還沒有進入到 scene 裡面的事件、然後再把事件轉換為 QGraphicsSceneMouseEvent 、讓 scene 去處理的。也因此,這邊的事件介面,和 scene 和 item 都不一樣,而是和 Qt 其他一般的 widget 相同。

在這個狀況下,相較於 scene / item 所得到的 QGraphicsSceneMouseEventQMouseEvent 所包含的資料就比較少了∼不過基本上,應該都還是夠用的。而細節就請大家自己看文件,不在這邊提了。

而在這個範例程式裡,Heresy 除了先輸出滑鼠的位置(view 座標系統)外,也另外透過 QGraphicsViewitemAt() 來試著取得滑鼠所在位置最上層的 item(只會取得一個 item,和 items() 會把所有在這個位置的都列出來不同);而如果這個位置有 item 的話,則再透過 QGraphicsViewcenterOn() 這個函式、調整 view 元件所顯示的區域,讓這個 item 顯示在 view 的中央。

同樣地,為了讓 QGraphicsView 能夠將這個事件轉譯給所使用的 scene,這邊也需要再去呼叫 QGraphicsViewmouseDoubleClickEvent(),把事件繼續傳遞下去。不過要注意的是,Heresy 這邊把呼叫這個函式的程式放在呼叫 centerOn() 之前;這是因為如果放在呼叫 centerOn() 之後的話,會因為 view 的顯示區域已經變了,導致傳給 scene 的資訊(滑鼠位置的部分)會和本來不一樣,導致有可能不會點在 item 上、而不會觸發到 item 的 mouseDoubleClickEvent() 事件。

 

主程式

這邊測試用的主程式,簡單地修改成:

int main( int argc, char **argv )
{
// Qt Application
QApplication app( argc, argv );

// 1. Qt Scene
CScene scene;
scene.setSceneRect( -500, -300, 1000, 600 );

// 2a. add customize item 1
CItem* pItem1 = new CItem();
scene.addItem( pItem1 );

// 2b. add customize item 2
CItem* pItem2 = new CItem();
scene.addItem( pItem2 );
pItem2->translate( 50, 50 );
pItem2->rotate( 45 );

// 3. Qt View
CGView view;
view.setScene( &scene );
view.resize( 320, 240 );
view.show();

// start!
return app.exec();
}

和之前的例子相比,這邊的 Qt Graphics View 架構的元件全部換成自己定義的版本了∼

而這樣的程式執行後,如果在自訂的 item 上用滑鼠左鍵點兩下的話,除了 CView 的顯示範圍就會被自動修改、以那個被點的 item 當作畫面的正中央外,此時背景的 console 視窗應該也會出現類似下面的訊息:

Click on view: (149,117)
Click on scene: (49,57)
There are 1 items.
Click on item: (4.24264,5.65685)

而從訊息輸出的順序,也可以看的出來,事件被處理的順序依序為:view→scene→item。而這邊的座標由於也都是各自輸出各自座標系統下的位置,所以雖然都是同一點,但是值卻都不相同。

 

這篇 Qt Graphics View Framework 的事件處理範例大概就先寫到這了。實際上,這類的事件處理架構,都是可以視需要、處理更多更複雜的事情的!不過由於這邊只是透過一個簡單的例子,來寫一個簡單的範例,所以暫時就先這樣就結束了。真的有更複雜的功能需求的話,應該是得去研究看看這些對應的類別,還有什麼函式可以用了。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。