2008年7月10日 星期四

QT-chap10

這一章要了解如何使用快速鍵pixmap

* t10/lcdrange.h包含LCDRange類別定義。
* t10/lcdrange.cpp包含LCDRange類別實現。
* t10/cannon.h包含CannonField類別定義。
* t10/cannon.cpp包含CannonField類別實現。
* t10/main.cpp包含MyWidget和main。

cannon.h

CannonField現在除了角度又多了一個力量值。

  int angle() const { return ang; }
  int force() const { return f; }

public slots:
  void setAngle( int degrees );
  void setForce( int newton );

signals:
  void angleChanged( int );
  void forceChanged( int );

力量的介面的實現和角度一樣。


private:
  QRect cannonRect() const;

我們把加農炮封裝的矩形的定義放到了一個單獨的函數中。


int ang;
int f;
};

力量被存儲到一個整數f中。

cannon.cpp

#include <qpixmap.h>

我們包含了QPixmap類別定義。
一段QPixmap的解示
The QPixmap class is an off-screen, pixel-based paint device.
QPixmap is one of the two classes Qt provides for dealing with images; the other is QImage. QPixmap is designed and optimized for drawing; QImage is designed and optimized for I/O and for direct pixel access/manipulation. There are (slow) functions to convert between QImage and QPixmap: convertToImage() and convertFromImage().


CannonField::CannonField( QWidget *parent, const char *name ) : QWidget( parent, name )
{
  ang = 45;
  f = 0;
  setPalette( QPalette( QColor( 250, 250, 200) ) );
}

力量(f)被初始化為0。


void CannonField::setAngle( int degrees )
{
  if ( degrees < degrees =" 5;"> 70 )
    degrees = 70;
  if ( ang == degrees )
    return;
  ang = degrees;
  repaint( cannonRect(), FALSE );
  emit angleChanged( ang );
}

我們在setAngle()函數中做了一個小的改變。
它只重畫視窗元件中含有加農炮的一小部分。

透過:void QWidget::repaint ( const QRect & r, bool erase = TRUE ) [slots]
去重畫此單一炮台

FALSE參數說明在一個繪畫事件發送到窗口部件之前指定的矩形將不會被擦去。
(應該就是說把矩形直接蓋過去吧,這樣就不會有閃爍的部分了,我猜的!)
這將會使繪畫過程加速和平滑。
應該是說QT會比對新的圖與舊的圖的差異,然後,只畫出差異的部分


void CannonField::setForce( int newton )
{
  if ( newton < 0 )
    newton = 0;
  if ( f == newton )
    return;
  f = newton;
  emit forceChanged( f );
}

setForce()的實現和setAngle()很相似。
唯一的不同是因為我們不顯示力量值,我們不需要重畫視窗元件。


void CannonField::paintEvent( QPaintEvent *e )
{
  if ( !e->rect().intersects( cannonRect() ) )
    return;

rect():returns the rectangle that should be updated
表示回傳需要update的部分,若沒有應該就是null

通常這一個e是表示這一整個畫布;在我測式的結果是e會自動與X11合作,討論出哪一個部分需要更新,然後,會把這一個要更新的部分e,傳給paintEvent()。


像上面這一個圖片,當左上角的視窗往右移動的話,這麼來說,傳回要更新的部分在畫布的地方,就是本來被擋住的地方,因視窗移動而露出眼的地方,另外一提,因與X11的作用,當有必要時QT會自動呼叫paintEvent(),但是,若左視窗向左移動時,因QT不需要更新畫布,因此不會呼叫paintEvent。


因此,在這一個例子,只有兩個時候會呼叫paintEvent()
1. 像上面的例子,我們把左上角的視窗往右移時,QT會自動呼叫paintEvent()
2. 我們程式中執行repaint()

另外,這裡的paintEvent是CannonField的paintEvent()

intersects:Returns TRUE if this rectangle intersects with rectangle r (there is at least one pixel that is within both rectangles)
當有其中一個pixel有重疊到的話,就傳回true,再加個not,就變成false,因此,會執行下面的程式


QRect cr = cannonRect();
QPixmap pix( cr.size() ); // 建立一個與上一行指令產生的矩型一樣大小的pixmap,用來做為畫圖的緩衝

cannonRect() 回傳的是放置炮台的方塊,
在這一個方塊上設置一個pixmap,
所有的繪畫操作都在這個pixmap中完成(在背景中),
並且之後只用一步操作來把這個pixmap畫到螢幕上。

這是不閃爍繪畫的本質:一次準確地在每一個像素上畫。
若畫的再少一點,你會得到繪畫錯誤。
若畫的再多一點,你會得到閃爍。


pix.fill( this, cr.topLeft() );

我們用這個pixmap來充滿這個窗口部件的背景。
表示由cr物件的左上方的點開始放這一個pixmap物件
基本上,pixmap以被我們宣告為一個矩形的大小了(因為設定為跟左下角的矩形一樣)

void QPixmap::fill ( const QWidget * widget, const QPoint & ofs )
This is an overloaded member function, provided for convenience. It behaves essentially like the above function.

Fills the pixmap with the widget's background color or pixmap. If the background is empty, nothing is done.

The ofs point is an offset in the widget.

The point ofs is a point in the widget's coordinate system. The pixmap's top-left pixel will be mapped to the point ofs in the widget. This is significant if the widget has a background pixmap; otherwise the pixmap will simply be filled with the background color of the widget.


Example:

void CuteWidget::paintEvent( QPaintEvent *e )
{
  QRect ur = e->rect();// rectangle to update
  QPixmap pix( ur.size() ); // Pixmap for double-buffering
  pix.fill( this, ur.topLeft() ); // fill with widget background

  QPainter p( &pix );
  p.translate( -ur.x(), -ur.y() ); // use widget coordinate system
// when drawing on pixmap
// ... draw on pixmap ...


  p.end();

  bitBlt( this, ur.topLeft(), &pix );
}



QPoint QRect::topLeft () const

傳回矩型的左上角位置(應該就是左上角的座標)


QPainter p( &pix );
p.setBrush( blue );
p.setPen( NoPen );
p.translate( 0, pix.height() - 1 );
p.drawPie( QRect( -35,-35, 70, 70 ), 0, 90*16 );
p.rotate( -ang );
p.drawRect( QRect(33, -4, 15, 8) );
p.end();

我們就像第九章中一樣畫,但是現在我們是在pixmap上畫。
在這一點上,我們有一個繪畫工具變量和一個pixmap看起來相當正確,但是我們還沒有在屏幕上畫呢。
看起來就像是先在pixmap畫完,再一次畫到屏幕上


p.begin( this );
p.drawPixmap( cr.topLeft(), pix );

所以我們在CannonField上面打開繪圖工具並在這之後畫這個pixmap。
這就是全部了。在頂部和底部各有一對線,並且這個代碼是100%不閃爍的。
是呼叫此function:void QPainter::drawPixmap ( const QPoint & p, const QPixmap & pm )

這一個原理就是先在pixmap上畫好圖之後,再一次畫到指定的地方,這樣就不會閃爍了。

這裡在對end與begin函數解示一下

bool QPainter::end ()

結束繪製。繪製時使用的任何資源都被釋放。

注意雖然你幾乎不需要調用end(),析構函數將會執行它,但是至少還有一種情況需要它,就是雙重緩衝。

QPainter p( myPixmap, this )
// ...
p.end(); // 停止在myPixmap上的繪製
p.begin( this );
p.drawPixmap( myPixmap );


因為當它正在被繪製的時候,你不能繪製一個QPixmap,它需要關閉激活的繪製工具。



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

這個函數傳回一個在視窗元件坐標中封裝加農炮的矩形。
首先我們創建一個50*50大小的矩形,然後移動它,使它的左下角和視窗元件自己的左下角相等。

QWidget::rect()函數在窗口部件自己的坐標(左上角是0,0)中返回視窗元件封裝的矩形。

t10/main.cpp


LCDRange *force = new LCDRange( this, "force" );
force->setRange( 10, 50 );

我們加入了第二個LCDRange,用來設置力量。


connect( force, SIGNAL(valueChanged(int)),cannonField, SLOT(setForce(int)) );
connect( cannonField, SIGNAL(forceChanged(int)),force, SLOT(setValue(int)) );

我們把force窗口部件和cannonField窗口部件連接起來,就和我們對angle窗口部件做的一樣。


QVBoxLayout *leftBox = new QVBoxLayout;
grid->addLayout( leftBox, 1, 0 );
leftBox->addWidget( angle );
leftBox->addWidget( force );

在第九章,我們把angle放到了佈局的左下單元。現在我們想在這個單元中放入兩個窗口部件,所一個我們用了一個垂直的盒子,把這個垂直的盒子放到這個網格單元中,並且把angle和force放到這個垂直的盒子中。


force->setValue( 25 );

我們初始化力量的值為25。

homework:
1. 讓加農炮的炮筒的大小依賴於力量:在cannon.cpp裡的paintEvent()裡面在製造炮筒的寬度相依於f;並於setForce()加入repaint(cannonRect(),FALSE);
2. 用+和-來增加或者減少力量:參考1參考2參考3
雖然我先找到參考2的列表,但是,英文太差,我還是沒有找到+、-分別由什麼代表,直到找到參考3才解決homework的問題!

沒有留言: