2008年7月9日 星期三

QT-chap 8

這一個章節要了解如何畫圖,請參考QT - Paint

包含的檔案:
/lcdrange.h包含LCDRange類別定義。
/lcdrange.cpp包含LCDRange類別實現。
/cannon.h包含CannonField類別定義。
/cannon.cpp包含CannonField類別實現。
/main.cpp包含MyWidget和main。

lcdrange.h
跟上一章比起來只我們多增加一個slots:setRange()
我們可以設定LCDRange的可能範圍
另外,此valueChanged(int)目前這一個範例我們只有宣告,在cpp檔中還沒有實作
應該在之後的章節會實作。
在C++中,function可以先在header file中先宣告,在.cpp檔中若沒有用到的話,可以先不用實作。

lcdrange.cpp

setFocusProxy( slider );

Sets the widget's focus proxy to widget slider. If slider is 0, the function resets this widget to have no focus proxy.
設置這個視窗元件的焦點代理為視窗元件w。如果w為0,這個函數重置這個視窗元件沒有焦點代理。基本上呢,應該是要用在可以接收keyboard的input的設置。
而我認為只有當slider的父視窗元件獲得焦點時,slider才會獲得焦點。


void LCDRange::setRange( int minVal, int maxVal )
{
  if ( minVal > 99 || maxVal < 0 || minVal > maxVal ) {
    qWarning( "LCDRange::setRange(%d,%d)\n"
      "\tRange must be 0..99\n"
      "\tand minVal must not be greater than maxVal",
      minVal, maxVal );
    return;
  }
  setRange( minVal, maxVal );
}


我們使用Qt的qWarning()函數來向用戶發出警告並立即返回。
qWarning()是一個像printf一樣的函數,默認情況下它的輸出發送到stderr。

cannon.h
這一個程式碼用到幾個新的類別
QSizePolicy:
The QSizePolicy class is a layout attribute describing horizontal and vertical resizing policy. More...

QPaintEvent:
The QPaintEvent class contains event parameters for paint events. More...


protected:
  void paintEvent( QPaintEvent * );

這是我們在QWidget中遇到的許多事件處理器中的第二個。
只要一個視窗元件需要刷新它自己(比如,畫窗口部件表面),這個虛函數就會被Qt調用。
這裡在說明一下public、private、friend、protected的分別
public:所有的類別都有存取權
private:除了本類別外的function,其它都不能存取。及便是衍生類別也是沒有權限存取
protected:您可以宣告這些成員為「受保護的成員」(protected member),保護的意思表示存取它有條件限制以保護該成員,當您將類別成員宣告為受保護的成員之後,繼承它的類別就可以直接使用這些成員,但這些成員仍然受到類別的保護,不可被物件直接呼叫使用。
friend:例如:現在宣告函數display為類別的product的友誼,即display可以存取類別product的物件中所有內部資料,所以可以存取傳入到display中的product物件私有成員number

cannon.cpp:建立繪圖區

CannonField::CannonField( QWidget *parent, const char *name ) : QWidget( parent, name )
{
  ang = 45; // 建構子初始化角度為45度
  setPalette( QPalette( QColor( 250, 250, 200) ) ); // 設定調色板的背景顏色
}

建構子把角度值初始化為45度並且給這個視窗元件設置了一個自定義調色板。

這個調色板只是說明背景色,並選擇了其它合適的顏色。(對於這個視窗元件,只有背景色和文本顏色是要用到的。)


void CannonField::setAngle( int degrees )
{
  if ( degrees < 5 )
    degrees = 5;
  if ( degrees > 70 )
    degrees = 70;
  if ( ang == degrees )
    return;
  ang = degrees;
  repaint(); // 用背景色填滿畫布,再呼叫paintEvent
  emit angleChanged( ang );
}

這是一個設置角度的函數,
當數值有改變時,
呼叫repaint()重畫畫板:沒有參數,函數清空窗口元件(通常用背景色來充滿)並向窗口元件發出一個繪畫事件。這樣的結構就是調用窗口部件的繪畫事件函數一次。
並且送出一個angleChanged(ang)的訊號:emit angleChanged( ang );

QWidget::repaint()函數清空視窗元件(通常用背景色來充滿)並向視窗元件發出一個繪畫事件。
這樣的結構就是呼叫視窗元件的繪畫事件函數一次。
最後,我們發射angleChanged()信號來告訴外面的世界,角度值發生了變化。
emit關鍵字只是Qt中的關鍵字,而不是標準C++的語法。實際上,它只是一個巨集。

repaint()通過立即呼叫paintEvent()來直接重新繪製視窗元件,
除非更新是失效的或者視窗元件被隱藏。

請參考Qt - QPainter

QPainter p( this );
表示建立一個QPainter物件,且其上層視窗元件是this,也就是cannonField


QSizePolicy CannonField::sizePolicy() const
{
  return QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
}

是傳回以建構子QSizePolicy ( SizeType hor, SizeType ver, bool hfw = FALSE )建立的物件,第三個參數是使用預設值。
QSizePolicy::Expanding - the sizeHint() is a sensible size, but the widget can be shrunk and still be useful. The widget can make use of extra space, so it should get as much space as possible (e.g. the horizontal direction of a slider).

也就是當子元件太大時,父元件可以自動延展。

main.cpp

class MyWidget: public QWidget
{
  public:
    MyWidget( QWidget *parent=0, const char *name=0 );
};

這一次我們在頂層視窗元件中只使用了一個LCDRange和一個CanonField。


LCDRange *angle = new LCDRange( this, "angle" );
angle->setRange( 5, 70 );

在構造函數中,我們創建並設置了我們的LCDRange。
我們設置LCDRange能夠接受的範圍是5-70度。


CannonField *cannonField = new CannonField( this, "cannonField" );

我們創建了我們的CannonField。


connect( angle, SIGNAL(valueChanged(int)),
cannonField, SLOT(setAngle(int)) );
connect( cannonField, SIGNAL(angleChanged(int)),
angle, SLOT(setValue(int)) );

這裡我們把LCDRange的valueChanged()信號和CannonField的setAngle()槽連接起來了。
只要用戶操作 LCDRange,就會刷新CannonField的角度值。我們也把它反過來連接了,這樣CannonField中角度的變化就可以刷新 LCDRange的值。
在我們的例子中,我們從來沒有直接改變CannonField的角度,但是通過我們的最後一個connect()我們就可以確保沒有任何變化可以改變這兩個值之間的同步關係。
注意只有當角度確實發生變化時,才發射angleChanged()是多麼的重要。如果LCDRange和CanonField都省略了這個檢查,這個程序就會因為第一次數值變化而進入到一個無限循環當中。


QGridLayout *grid = new QGridLayout( this, 2, 2, 10 );
//2×2,10像素的邊界

到現在為止,我們沒有因為幾何管理把QVBox和QGrid窗口部件集成到一起。
現在,無論如何,我們需要對我們的佈局加一些控制,所以我們使用了更加強大的QGridLayout類。
QGridLayout不是一個窗口部件,它是一個可以管理任何窗口部件作為子對象的不同的類。

就像註釋中所說的,我們創建了一個以10像素為邊界的2*2的數組。(QGridLayout的構造函數有一點神秘,所以最好在這裡加入一些註釋。)


grid->addWidget( quit, 0, 0 );

我們在網格的左上的單元格中加入一個Quit按鈕:0,0。


grid->addWidget( angle, 1, 0, Qt::AlignTop );

我們把angle這個LCDRange放到左下的單元格,在單元格內向上對齊。(這只是QGridLayout所允許的一種對齊方式,而QGrid不允許。)


grid->addWidget( cannonField, 1, 1 );

我們把CannonField對象放到右下的單元格。(右上的單元格是空的。)


grid->setColStretch( 1, 10 );

我們告訴QGridLayout右邊的列(列1)是可拉伸的。(左邊是列0)
因為左邊的列不是(它的拉伸因數是0,這是默認值),QGridLayout就會在MyWidget被重新定義大小的時候試圖讓左面的窗口部件大小不變,而重新定義CannonField的大小。


angle->setValue( 60 );

我們設置了一個初始角度值。注意這將會引發從LCDRange到CannonField的連接。


angle->setFocus();

我們剛才做的是設置angle獲得鍵盤焦點,
在這種情況下鍵盤輸入會到達LCDRange視窗元件。
LCDRange沒有包含任何keyPressEvent(),所以這看起來不太可能有用。無論如何,它的構造函數中有了新的一行:

setFocusProxy( slider );

LCDRange設置slider作為它的focus proxy。
也就是說當由LCDRange接收到keyborad的輸入時,
動作會由slider表現出來
QSlider有一個相當好的鍵盤介面,所以就會出現我們給LCDRange添加的這一行。

homework:
把AlignTop刪掉,左邊的LCDNumber高度會變大
給左面的列一個非零的拉伸因數,左邊會因為整個視窗變化而變化
試著在QButton::setText()調用中把「Quit」改為「&Quit」。Quit的Q會有底線。
這個時候按Alt+Q會變成這一個按鈕的快捷鍵。
把CannonField的文本放到中間:QWidget類別有一個rect()可以傳回畫布的大小
rect().height():可以得到畫布的高度
rect().width():可以得到畫布的寬度

沒有留言: