2008年7月12日 星期六

QT-chap 11

這一章要學會QTimerRegion
* t11/lcdrange.h包含LCDRange類別定義。
* t11/lcdrange.cpp包含LCDRange類別實現。
* t11/cannon.h包含CannonField類別定義。
* t11/cannon.cpp包含CannonField類別實現。
* t11/main.cpp包含MyWidget和main。

cannon.h

現在炮台有射擊能力了


void shoot();

當炮彈沒有在空中時,使用這一個slots射擊炮彈


private slots:
  void moveShot();

當炮彈在空中時,這一個private slots function使用timer來移動炮彈的位置


private:
  void paintShot( QPainter * );

透過這一個private function來畫在空中移動的炮彈


QRect shotRect() const;

當炮彈在空中時,這一個private function回傳它所佔用的空間的矩形
若離開了顯示畫面,回傳一個沒有定義的矩形


  int timerCount;
  QTimer * autoShootTimer;
  float shoot_ang;
  float shoot_f;
};

這是包留炮彈射出去時的資訊,
timerCount表示射出炮彈後所經過的時間,
shoot_ang表示炮彈射出去時的角度
shoot_f表示炮彈射出去的力量

cannon.cpp


CannonField::CannonField( QWidget *parent, const char *name ) : QWidget( parent, name )
{
  ang = 45;
  f = 0;
  timerCount = 0;
  autoShootTimer = new QTimer( this, "movement handler" );
  connect( autoShootTimer, SIGNAL(timeout()), this, SLOT(moveShot()) );
  shoot_ang = 0;
  shoot_f = 0;
  setPalette( QPalette( QColor( 250, 250, 200) ) );
}

初始化我們的非公開的變數,並且新增一個timer,當timer發出timeout時,執行moveShot的function


void CannonField::shoot()
{
  if ( autoShootTimer->isActive() )
    return;
  timerCount = 0;
  shoot_ang = ang;
  shoot_f = f;
  autoShootTimer->start( 50 );
}

只有當炮彈不在空中時,此函數才有效果,
炮彈射擊後所經過的時間歸零;並且記錄炮彈射出去時的角度與力量;並開始timer,每50ms一個timerout


void CannonField::moveShot()

這一個function是用來畫出當炮彈在空中時所應該在的位置
此函數的任務就是計算並且畫出炮彈的位置,當QTimer啟動時,每50ms會被呼叫一次,
還有決定是否要停止QTimer的執行


QRegion r( shotRect() );

首先,QRegion可以保留任何Region,我們透過QRegion來保留舊的炮彈位置。


timerCount++;

增加炮彈射出後的時間


QRect shotR = shotRect();

shotRect()函數會傳回炮彈的矩形,這之間包括炮彈的新位置


if ( shotR.x() > width() || shotR.y() > height() )
  autoShootTimer->stop();
else
  r = r.unite( QRegion( shotR ) );
repaint( r ); // 會呼叫paintEvent()函數


當炮彈新的位置移動到下面或是右邊的邊界時,
我們就停止QTimer的執行,
否則我們則添加新的shotRect()到QRegion中
最後我們重畫QRegion
r.unite( QRegion( shotR ) ),是表示舊的炮彈與新的炮彈的聯集形成的區域
repaint( r )要求要重畫r這一個區域


void CannonField::paintEvent( QPaintEvent *e )
{
  QRect updateR = e->rect();
  QPainter p( this );

  if ( updateR.intersects( cannonRect() ) )
    paintCannon( &p );
  if ( autoShootTimer->isActive() && updateR.intersects( shotRect() ) ) // 很重要,當autoShootTimer被stop後,炮彈就不會被畫出來了
    paintShot( &p );
}


實作畫圖事件,當執行repaint時,則會呼叫paintEvent


QRect updateR = e->rect();

rect()的解示是說傳回需要更新的部分。
若要更新的部分與炮台有交集的話,就呼叫paintCannon()
若要更新的部分與炮彈有交集的話,就呼叫paintShot()


QPainter p( this );

把QPainter加入,要繪製的設備是this,也就是QWidget


if ( updateR.intersects( cannonRect() ) )
  paintCannon( &p );

updateR表示需要更新的部分,而cannonRect()回傳的是放置炮台的這一個方塊區域
先判斷是否兩個有交集,若有交集則執行paintCannon,更新炮台畫面


if ( autoShootTimer->isActive() && updateR.intersects( shotRect() ) )
  paintShot( &p );

若炮彈在空中,且需要更新的部分與新的炮彈位置有交集,則重畫炮彈的圖示


void CannonField::paintShot( QPainter *p )
{
  p->setBrush( black );
  p->setPen( NoPen );
  p->drawRect( shotRect() );
}

這一個非公開函數是在準備畫炮彈,黑色的炮彈、沒有邊界線、並且傳回炮彈的位置


void CannonField::paintCannon( QPainter *p )
{
  QRect cr = cannonRect(); // 回傳左下角炮台的矩型
  QPixmap pix( cr.size() ); // 設定一個pixmap的大小與左下角的炮台一樣的大小
  pix.fill( this, cr.topLeft() ); // 把這一個大小的pixmap左上角座標設定成左下角炮台的左上角座標

  QPainter tmp( &pix ); // 把pix設定為等一下要畫在上面的設備
  tmp.setBrush( blue ); // 設定為藍色
  tmp.setPen( NoPen ); // 沒有外框

  tmp.translate( 0, pix.height() - 1 ); // 座標點往下移pix.height()-1
  tmp.drawPie( QRect( -35,-35, 70, 70 ), 0, 90*16 ); // 在一個方型[QRect( -35,-35, 70, 70 )]中畫一個起始為0度,共90度的扇型
  tmp.rotate( -ang ); // 依新的原點旋轉
  tmp.drawRect( barrelRect ); // 畫炮筒
  tmp.end(); // 結束pixmap的畫圖,也就是說已畫在pixmap設備上了
// 因為,上面的是tmp.end();所以,下面是p,跟上面不同,不需要p.begin();
  p->drawPixmap( cr.topLeft(), pix ); // 把pixmap畫到qprinter(這裡指的是CannonField)
}

paintCannon()與前一章中的paintEvent()不同的地方在於tmp.end()後,p不需要begin();前一章因為是同樣個p,所以,當end後,要把pixmap畫到畫布上,必需要begin


const QRect barrelRect(33, -4, 15, 8);


我上ppt了問了一下高手,了解這一行的意思了。
定義barrelRect為一個QRect
並使用(33, -4, 15, 8)初始化barrelRect
(33,-4,15,8)是建構元的參數
const不是#define的意思 他代表barrelRect是constant
也就是回傳的QRect是不能被修改的。

反正,關鍵字barrelRect就是QRect(33,-4,15,8)的這一個物件。


QRect CannonField::cannonRect() const
{
  QRect r( 0, 0, 50, 50 );
  r.moveBottomLeft( rect().bottomLeft() );
  return r;
}

這裡的rect()應該是this.rect()的省略


QRect CannonField::shotRect() const
{
  const double gravity = 4;

  double time = timerCount / 4.0;
  double velocity = shoot_f;
  double radians = shoot_ang*3.14159265/180;

  double velx = velocity*cos( radians );
  double vely = velocity*sin( radians );
  double x0 = ( barrelRect.right() + 5 )*cos(radians);
  double y0 = ( barrelRect.right() + 5 )*sin(radians);
  double x = x0 + velx*time;
  double y = y0 + vely*time - 0.5*gravity*time*time;

  QRect r = QRect( 0, 0, 6, 6 );
  r.moveCenter( QPoint( qRound(x), height() - 1 - qRound(y) ) );
  return r;
}

計算炮彈球中心的位置,使用到重力加速度、射出炮彈的角度、力量和射出去後經過的時間
moveCenter(QPoint &)設置方塊的中心點,而大小不改變
qRound()函數是一個在qglobal.h中定義的inline function(被其它所有Qt頭文件包含)。
qRound()把一個雙精度實數變為最接近的整數。

main.cpp


QPushButton *shoot = new QPushButton( "&Shoot", this, "shoot" );
shoot->setFont( QFont( "Times", 18, QFont::Bold ) );

增加一個shoot的按鈕


connect( shoot, SIGNAL(clicked()), cannonField, SLOT(shoot()) );

把Shoot按鈕的clicked() signal和CannonField的shoot() slot連接起來。

homework:
用一個填充的圓來表示炮彈:用QPainter::drawEllipse()
用一個改變顏色的球:p->setBrush( QColor(rand()%255,rand()%255,rand()%255 ));

沒有留言: