2008年7月21日 星期一

QT-chap 14

這一個章節要了解如何使用QMouseEvent:mousePressEvent、mouseMoveEvent、mouseReleaseEvent;QWMatrixQAccel

這裡要注意的是, mouse的任何事件都是由系統幫我們偵測,而我們要做的部分就是實作每一個事件後應該要做的事情。

cannon.h

CannonField現在可以接收滑鼠事件,使得用戶可以通過點擊和拖拽炮筒來瞄準。
CannonField也有一個障礙物的牆。


protected:
  void paintEvent( QPaintEvent * );
  void mousePressEvent( QMouseEvent * );
  void mouseMoveEvent( QMouseEvent * );
  void mouseReleaseEvent( QMouseEvent * );

除了常見的事件處理器,CannonField實現了三個滑鼠事件處理器。名稱說明了一切。


void paintBarrier( QPainter * );

這個非公開函數繪製了障礙物牆。


QRect barrierRect() const;

這個私有函數返回封裝障礙物的矩形。


bool barrelHit( const QPoint & ) const;

這個私有函數檢查是否一個點在加農炮炮筒的內部。


bool barrelPressed;

當用戶在炮筒上點擊滑鼠並且沒有放開的話,這個私有變量為TRUE。

cannon.cpp


barrelPressed = FALSE;

一開始在建構子中,表示一開始滑鼠沒有在炮筒上點左鍵


} else if ( shotR.x() > width() || shotR.y() > height() || shotR.intersects(barrierRect()) ) {

有了障礙物之後,我們有三種表示沒有命中的判斷情況


void CannonField::mousePressEvent( QMouseEvent *e )
{
  if ( e->button() != LeftButton )
    return;
  if ( barrelHit( e->pos() ) )
    barrelPressed = TRUE;
}

這是QT的事件處理器,當滑鼠指標在視窗元件上按滑鼠按鍵時,
此函數會被呼叫。
一開始先判斷是不是按左鍵,若不是的話,則跳出。
我們檢查滑鼠標指針是否在加農炮的炮筒內。
如果是的,我們設置barrelPressed為TRUE。
注意pos()函數返回的是窗口元件坐標系統中的點。


void CannonField::mouseMoveEvent( QMouseEvent *e )
{
  if ( !barrelPressed )
    return;
  QPoint pnt = e->pos();
  if ( pnt.x() <= 0 )
    pnt.setX( 1 );
  if ( pnt.y() >= height() )
    pnt.setY( height() - 1 );
  double rad = atan(((double)rect().bottom()-pnt.y())/pnt.x());
  setAngle( qRound ( rad*180/3.14159265 ) );
}


透過移動滑鼠去計算炮台的角度~

這是另外一個Qt事件處理器。
當用戶已經在窗口部件中按下了滑鼠按鍵並且移動/拖拽滑鼠時,它被調用。
(你可以讓Qt在沒有滑鼠按鍵被按下的時候發送滑鼠移動事件。請看QWidget::setMouseTracking()。)

這個處理器根據鼠標指針的位置重新配置加農炮的炮筒。

首先,如果炮筒沒有被按下,我們返回。
接下來,我們獲得滑鼠指針的位置。
如果滑鼠指針到了視窗元件的左面或者下面,我們調整滑鼠指針使它返回到視窗元件中。

然後我們計算在滑鼠指針和視窗元件的左下角所構成的虛構的線和窗口部件下邊界的角度。最後,我們把加農炮的角度設置為我們新算出來的角度。

記住要用setAngle()來重新繪製加農炮。

public static atan(tangent:Number) :Number:計算並傳回參數 tangent 所指定正切值的角度值,以弧度為單位。傳回的值介於負的 pi 除以2與正的 pi 除以2之間。


void CannonField::mouseReleaseEvent( QMouseEvent *e )
{
  if ( e->button() == LeftButton )
    barrelPressed = FALSE;
}

只要用戶釋放滑鼠按鈕並且它是在視窗元件中按下的時候,這個Qt事件處理器就會被調用。
如果鼠標左鍵被釋放,我們就會確認炮筒不再被按下了。

繪畫事件包含了下述額外的兩行:

if ( updateR.intersects( barrierRect() ) )
  paintBarrier( &p );


paintBarrier()做的和paintShot()、paintTarget()和paintCannon()是同樣的事情。

void CannonField::paintBarrier( QPainter *p )
{
  p->setBrush( yellow );
  p->setPen( black );
  p->drawRect( barrierRect() );
}

這個私有函數用一個黑色邊界黃色填充的矩形作為障礙物。


QRect CannonField::barrierRect() const
{
  return QRect( 145, height() - 100, 15, 100 );
}

這個私有函數返回障礙物的矩形。我們把障礙物的下邊界和視窗元件的下邊界放在了一起。


bool CannonField::barrelHit( const QPoint &p ) const
{
  QWMatrix mtx;
  mtx.translate( 0, height() - 1 );
  mtx.rotate( -ang );
  mtx = mtx.invert();
  return barrelRect.contains( mtx.map(p) ); // 先把p透過mtx.map()轉換成新的座標,再判斷barrelRect是否有被包含這一個轉換後的座標
}

如果點在炮筒內,這個函數返回TRUE;否則它就返回FALSE。

這裡我們使用QWMatrix類。它是在頭文件qwmatrix.h中定義的,這個頭文件被qpainter.h包含。
QWMatrix定義了一個坐標系統映射。我把它想成一個存在一個轉換矩陣,正常的座標可以透過它轉換成新的座標。QWMatrix.map()就是透過QWMatrix轉換座標。

它可以執行和QPainter中一樣的轉換。
這裡我們實現同樣的轉換的步驟就和我們在paintCannon()函數中繪製炮筒的時候所作的一樣。首先我們轉換坐標系統,然後我們旋轉它。
現在我們需要檢查點p(在窗口部件坐標系統中)是否在炮筒內。為了做到這一點,我們inverse這個轉換矩陣。倒置的矩陣就執行了我們在繪製炮筒時使用的inverse的轉換。我們通過使用反矩陣來映射點p,並且如果它在初始的炮筒矩形內就返回TRUE。

gamebrd.cpp


#include <qaccel.h>

QAccel類別是用來處理鍵盤的加速鍵和快捷鍵
鍵盤加速鍵是在某個組合鍵按下的時候出發一個動作,加速鍵可以處理窗口部件和它子部件裡所有的鍵盤動作所以它不會被鍵盤焦點所影響。


QVBox *box = new QVBox( this, "cannonFrame" );
box->setFrameStyle( QFrame::WinPanel | QFrame::Sunken );
cannonField = new CannonField( box, "cannonField" );

我們創建並設置一個QVBox,設置它的框架風格,並在之後創建CannonField作為這個盒子的子對象。
因為沒有其它的東西在這個盒子裡了,效果就是QVBox會在CannonField周圍生成了一個框架。


QAccel *accel = new QAccel( this );
accel->connectItem( accel->insertItem( Key_Enter ), this, SLOT(fire()) );
accel->connectItem( accel->insertItem( Key_Return ), this, SLOT(fire()) );

現在我們創建並設置一個加速鍵。
加速鍵就是在應用程序中截取鍵盤事件並且如果特定的鍵被按下的時候呼叫相應的slot。
這種機制也被稱為快捷鍵。
注意快捷鍵是窗口部件的子對象並且當窗口部件被銷毀的時候銷毀。
QAccel不是窗口部件,並且在它的父對象中沒有任何可見的效果。

我們定義兩個快捷鍵。我們希望在Enter鍵被按下的時候調用fire()槽,在Ctrl+Q鍵被按下的時候,應用程序退出。
因為Enter有時又被稱為Return,並且有時鍵盤中兩個鍵都有,所以我們讓這兩個鍵都調用fire()。


accel->connectItem( accel->insertItem( CTRL+Key_Q ), qApp, SLOT(quit()) );

並且之後我們設置Ctrl+Q和Alt+Q做同樣的事情。一些人通常使用Ctrl+Q更多一些(並且無論如何它顯示了如果做到它)。
CTRL、Key_Enter、Key_Return和Key_Q都是Qt提供的常量。它們實際上就是Qt::Key_Enter等等,但是實際上所有的類都繼承了Qt這個命名空間類。


QGridLayout *grid = new QGridLayout( this, 2, 2, 10 );
grid->addWidget( quit, 0, 0 );
grid->addWidget( box, 1, 1 );
grid->setColStretch( 1, 10 );

我們放置box(QVBox),而不是CannonField,在右下的單元格中。

沒有留言: