2008年7月14日 星期一

QT-chap 12

這一章要學會rand

t12/lcdrange.h
LCDRange現在有標籤了

class QLabel;

我們先聲明QLabel,因為,我們要用到QLabel的指標,我們之前說過,因為,目前還沒有要實作QLabel,我們目前只有使用QLabel的指標,目前,我們不需要包含qlabel.h這一個頭檔,只先宣告class QLabel,可以加速compiler的速度。


class LCDRange : public QVBox
{
  Q_OBJECT
  public:
    LCDRange( QWidget *parent=0, const char *name=0 );
    LCDRange( const char *s, QWidget *parent=0, const char *name=0 );

這裡我們宣告另一個建構函數,與原本不同的是,新增的建構函數多了一個標籤。


const char *text() const;

回傳標籤字串


void setText( const char * );

設置標籤字串


private:
  void init();

因為我們有兩個建構子,我們選擇把通常的初始化設定在非公開的init函數中

lcdrange.cpp


LCDRange::LCDRange( QWidget *parent, const char *name ) : QVBox( parent, name )
{
  init();
}

這一個建構函數使用最基本的初始化,同時,我們把最基本的初始化設定在init函數中


LCDRange::LCDRange( const char *s, QWidget *parent, const char *name ) : QVBox( parent, name )
{
  init();
  setText( s );
}

這一個建構函數除了使用最基本的初始化之外,還另外設置了標籤的內容


void LCDRange::init()
{
  QLCDNumber *lcd = new QLCDNumber( 2, this, "lcd" );
  slider = new QSlider( Horizontal, this, "slider" );
  slider->setRange( 0, 99 );
  slider->setValue( 0 );

  label = new QLabel( " ", this, "label" );
  label->setAlignment( AlignCenter );

  connect( slider, SIGNAL(valueChanged(int)),lcd, SLOT(display(int)) );
  connect( slider, SIGNAL(valueChanged(int)), SIGNAL(valueChanged(int)) );

  setFocusProxy( slider );
}

這裡的init與上一章的建構子的內容是一樣的
除了中間增加了一個設置QLabel的部分,並且讓它顯示的字串與中間對齊;但是,QLabel顯示的字串是"",也就是目前是沒有顯示東西啦


const char *LCDRange::text() const
{
  return label->text();
}

這一個函數會回傳一個字串指標


void LCDRange::setText( const char *s )
{
  label->setText( s );
}

這一個函數就是在設置label的顯示字串

cannon.h

CannonField現在有兩個新的信號:hit()和missed()。另外它還包含一個目標。


void newTarget();

這一個新的slot function生成一個新的目標


signals:
  void hit();
  void missed();

當射中目標時,就會觸發hit function
當炮彈到達右邊或下方的邊界時,就會觸發missed function
觸發是用emit這一個巨集


void paintTarget( QPainter * );

這一個非公開函數是在畫射擊目標的


QRect targetRect() const;

這一個函數傳回目標的方塊


QPoint target;

這一個非公開變數是以QPoint在記錄目標的中心點座標

cannon.cpp


#include <qdatetime.h>

因為,我們要隨機產生目標的位置,所以,
我們要設定rand的seed,
若沒有設定,或每一次seed都是一樣的話,那產生出來的結果就會一樣
因此透過datetime把seed設定成目前的時間,則每一次跑rand的結果就會不一樣


#include <stdlib.h>

因為我們需要用到rand()的函數


newTarget();

這一個函數是在替我們在一個隨機的位置上建立一個新的目標
並且在建構子上呼叫此函數,
在建構函數視窗元件還是不可見的,QT保証在不可見的視窗函數呼叫repaint是無害的


void CannonField::newTarget()
{
  static bool first_time = TRUE;// 當此函數被呼叫第二次時,此行沒有效果
  if ( first_time ) {
    first_time = FALSE;
    QTime midnight( 0, 0, 0 );
    srand( midnight.secsTo(QTime::currentTime()) );
  }
  QRegion r( targetRect() );
  target = QPoint( 200 + rand() % 190, 10 + rand() % 255 );
  repaint( r.unite( targetRect() ) ); // targetRect()會依target值來改變出現的位置
}

這裡對static關鍵字說明一下,
當類別中的資料成員使用static時,所有此類別的物件的此一變數都是共用同一個記憶體。
另一方面當函數宣告成static時,方法將成為類別的方法

另外,為了隨機產生位置,若不設定隨機種子的話,每次的結果都會是一樣的,
因此,我們必需設定隨機種子,並且,隨機種子的數值也必需每一次都不一樣,
所以,我們利用了時間函數,使得隨機種子的數值是由子時0點目前到秒數,所以,每一次執行的結果會不同。請參考:C++ - rand

並且設定目標的位置是x=200~389和y=35~289
注意rand()返回一個>=0的隨機整數


if ( shotR.intersects( targetRect() ) ) {
  autoShootTimer->stop();
  emit hit();

檢查炮彈(shotR)是否與目標(targetRect)是否相交,
若相交,則表示擊中,則我們停止計時器,並且觸發hit()


} else if ( shotR.x() > width() || shotR.y() > height() ) {
  autoShootTimer->stop();
  emit missed();

若離開下面的邊界或右邊的邊界,則表示沒有射中目標,則停止時間計時,並且觸發missed()


if ( updateR.intersects( targetRect() ) )
  paintTarget( &p );

這兩行確認目標是否要被重新繪製


void CannonField::paintTarget( QPainter *p )
{
  p->setBrush( red );
  p->setPen( black );
  p->drawRect( targetRect() );
}

繪出紅色有邊框的方塊


QRect CannonField::targetRect() const
{
  QRect r( 0, 0, 20, 10 );
  r.moveCenter( QPoint(target.x(),height() - 1 - target.y()) );
  return r;
}

一開始先建立一個在(0,0)座標的方塊,在移動到由target()計算出來的目標位置
並且回傳這一個方塊

main.cpp

在main.cpp中沒有新的成員,但是,我們在LCDRange class中有新增一個建構子
設置一個新的標籤
LCDRange *angle = new LCDRange( "ANGLE", this, "angle" );

homework:
創建一個作弊的按鈕,當按下它的時候,讓CannonField畫出炮彈在五秒中的軌跡:
建立一個enum SHOOT {shoot_mode,cheat_mode};
這是原本的connect( shoot, SIGNAL(clicked()), cannonField, SLOT(shoot()) );
這是新增的connect(cheat,SIGNAL(clicked()),cannonField,SLOT(cheat_shoot()));
把shoot()的內容全部都移動到新增的function:move()

void CannonField::shoot()
{
  mode = shoot_mode;
  move();
}


void CannonField::cheat_shoot()
{
  mode = cheat_mode;
  move();
}


void CannonField::moveShot()
{
  QRegion r( shotRect() );
  timerCount++;

  QRect shotR = shotRect();
  if (mode==cheat_mode && timerCount>20) // when the mode equal cheat_mode , then we determine the timerCount bigger than 10
  {
    autoShootTimer->stop();
  }
  else if ( shotR.intersects( targetRect() ) ) {
    autoShootTimer->stop();
    emit hit();
  } else if ( shotR.x() > width() || shotR.y() > height() ) {
    autoShootTimer->stop();
    emit missed();
  } else {
    r = r.unite( QRegion( shotR ) );
  }

  repaint( r );
}

沒有留言: