NOTICE

 任何跟文章無關的閒聊,請愛用 留言板(Guestbook)

 想要快速瀏覽主題,請點選單 目錄 標籤。

 停止更新ing,請見諒。 <(_ _)>


2月 04, 2009

【翻譯】Qt Tutorial 9 - With Cannon You Can

@
tutorials/tutorial/t9/cannonfield.cpp
tutorials/tutorial/t9/cannonfield.h
tutorials/tutorial/t9/lcdrange.cpp
tutorials/tutorial/t9/lcdrange.h
tutorials/tutorial/t9/main.cpp
tutorials/tutorial/t9/t9.pro

  在這個範例我們來畫一個可愛的藍色小加農砲。只有 cannonfield.cpp 不同於先前的章節。




Line by Line Walkthrough

t9/cannonfield.cpp

 void CannonField::paintEvent(QPaintEvent * /* event */)
 {
     QPainter painter(this);

  我們要開始認真地使用 QPainter 了。我們建立了一個操作這個元件的繪圖工具(painter)。

     painter.setPen(Qt::NoPen);

  QPainter 所繪的邊緣(edge)是使用畫筆(pen)畫出來的。這裡我們將之設成 Qt::NoPen,代表我們在畫出某些東西時,將不會有一個明確的邊。

     painter.setBrush(Qt::blue);

  當 QPainter 填滿一個矩形、圓形、或是其他任何東西,它會使用它的筆刷(brush)來填滿圖形。這裡我們將它設成使用純藍色筆刷。(我們也可以使用調色盤。)藍色筆刷將會填滿所有我們畫出來的東西的邊緣內部。

     painter.translate(0, rect().height());

  QPainter::translate() 函式轉化(translate)了 QPainter 的座標系統(換言之,就是藉由偏移量[offset]移動)。這裡我們將元件的左下角設為(0, 0),x 與 y 的方向仍然沒有改變。也就是說,現在所有在這個元件之內的 y 坐標都是負的。(關於 Qt 的座標系統的更多資訊請參見 The Coordinate System。)

     painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16);

  QPainter::drawPie() 函式使用一個起始角度以及弧長,在特定的矩形中畫一個圓餅的形狀。角度的單位是 16 分之一度。零度在三點鐘方向。畫的方向為逆時鐘。這裡我們在元件的左下角畫了一個四分之一的圓。這個圓餅被藍色填滿,而且沒有邊框。

     painter.rotate(-currentAngle);

  QPainter::rotate() 函式繞著 QPainter 的原點旋轉(rotate)座標系統。旋轉引數是一個由度數(不是上面所說的 16 分之一度)給定的浮點數(float),並且是順時鐘的。這裡我們順時鐘旋轉了 currentAngle 度。

     painter.drawRect(QRect(30, -5, 20, 10));

  QPainter::drawRect() 函式畫出一個特定的矩形。這裡我們畫出加農砲的砲管。

  當座標系統像上面這樣被轉換(轉化、旋轉、縮放[scale]、或是修剪[shear]),常常很難去想像畫出來的結果。

  在這個例子中,坐標系統先被轉化再被旋轉。若是矩形 QRect(30, -5, 20, 10) 被畫在這個轉換後的座標系統,它看起來會像這樣:



  注意矩形被 CannonField 元件的邊界修剪了一部份。當我們旋轉座標系統,舉 60 度為例,矩形將會繞著 (0, 0)--也就是左下角,因為我們轉化了座標系統--被旋轉。結果看起來像這樣:




Running the Application

  當滾動軸被操作時,畫出來的加農砲角度也會跟著改變。

  Quit 按紐的 'Q' 現在有條底線,而且 Alt+Q 會按下這個按鈕。


Exercises

  設定一支不同的畫筆代替 Qt::NoPen。設定一支調色盤的筆刷。

  試著將按鈕上的文字改成 "Q&uit" 或是 "Qu&it" 來代替 "&Quit"。發生了什麼事?


來源:Qt Tutorial 9 - With Cannon You Can
版本:4.4.3

【翻譯】Qt Tutorial 8 - Preparing for Battle

@
tutorials/tutorial/t8/cannonfield.cpp
tutorials/tutorial/t8/cannonfield.h
tutorials/tutorial/t8/lcdrange.cpp
tutorials/tutorial/t8/lcdrange.h
tutorials/tutorial/t8/main.cpp
tutorials/tutorial/t8/t8.pro

  在這個範例中,我們介紹了第一個可以描繪自身的元件。我們也會加入一個有用的鍵盤介面(使用兩行程式碼)。




Line by Line Walkthrough

t8/lcdrange.h

  這個檔案與第七章的 lcdrange.h 非常相似。我們加入了一個 slot:setRange()。

     void setRange(int minValue, int maxValue);

  我們現在增加了設定 LCDRange 範圍的可能性。到現在為止,它已經被固定在 0 到 99 之間。

t8/lcdrange.cpp

  這裡的建構子做了一點改變(我們稍後將會討論它)。

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

  setRange() slot 設定了 LCDRange 中滾動軸的範圍。因為我們已經把 QLCDNumber 設定為總是顯示兩個數字了,所以我們想限制 minValmaxVal 的可能範圍,以避免 QLCDNumber 溢出。(我們可以允許最小值到 -9,不過我們選擇不這麼做。)假如引數是非法的,我們使用 Qt 的 qWarning() 函式對使用者發出一個警告並直接返回。qWarning() 是一個預設輸出到 stderr、類似於 printf 函式。只要你願意,你可以使用 qInstallMsgHandler() 設置你自己的處理函式。

t8/cannonfield.h

  CannonField 是一個知道如何顯示自身的新自訂元件。

 class CannonField : public QWidget
 {
     Q_OBJECT

 public:
     CannonField(QWidget *parent = 0);

  CannonField 繼承自 QWidget。我們使用了與 LCDRange 相同的寫法。

     int angle() const { return currentAngle; }

 public slots:
     void setAngle(int angle);

 signals:
     void angleChanged(int newAngle);

  現在,CannonField 只包含一個角度(angle)值,我們採用了與 LCDRange 值相同的寫法提供它的介面。

 protected:
     void paintEvent(QPaintEvent *event);

  這是我們第二次遇到 QWidget 眾多事件處理器(handler)中的一個。無論一個元件在何時需要更新(update),這個虛擬函式就會被 Qt 呼叫。

t8/cannonfield.cpp

 CannonField::CannonField(QWidget *parent)
     : QWidget(parent)
 {

  再一次地,我們使用了與前一章 LCDRange 相同的寫法。

     currentAngle = 45;
     setPalette(QPalette(QColor(250, 250, 200)));
     setAutoFillBackground(true);
 }

  這個建構子將角度值初始化為 45 度,並為這個元件設定了一個自訂調色盤(palette)。

  這個調色盤使用了象徵性的顏色作為背景,並挑選其他適當的顏色。(對這個範例而言,只有背景跟文字的顏色會被真正用到。)我們呼叫 setAutoFillBackground(true) 以告知 Qt 自動填滿背景。

  QColor 是指定一組 RGB (red-green-blue,紅-綠-藍)值,其中每一個值都介於 0 (暗)到 255 (亮)之間。我們也可以使用像是 Qt::yellow 這種預先定義好的顏色,來代替指定 RGB 值。

 void CannonField::setAngle(int angle)
 {
     if (angle < 5)
         angle = 5;
     if (angle > 70)
         angle = 70;
     if (currentAngle == angle)
         return;
     currentAngle = angle;
     update();
     emit angleChanged(currentAngle);
 }

  這個函式設定了角度值。我們選擇 5 到 70 為合法範圍,並以此調整給定的角度數值。若新的角度值超出範圍,我們選擇不發出警告。

  假如新的角度值與舊的角度值相等,我們立即返回。只有在角度真正發生改變時,才發出 angleChanged() signal 是很重要的。

  這時我們設定了新的角度值,並重繪我們的元件。QWidget::update() 函式會清空這個元件(通常以背景色填滿),並送出一個繪圖(paint)事件給元件。這樣的結果就是呼叫了這個元件的繪圖事件函式。

  最後,我們發出 angleChanged() signal 告知外界角度值被改變了。這個 emit 關鍵字是 Qt 特有的,不是標準的 C++ 語法。事實上,它是一個巨集。

 void CannonField::paintEvent(QPaintEvent * /* event */)
 {
     QPainter painter(this);
     painter.drawText(200, 200,
                      tr("Angle = ") + QString::number(currentAngle));
 }

  這是我們第一次嘗試寫繪圖事件處理器。事件引數包含了有關繪圖事件的資訊,例如元件必須被更新的區域資訊。現在,讓我們懶惰點,直接畫出所有東西。

  我們的程式在元件的一個固定位置顯示角度值。為了實現它,我們建立了一個在 CannonField 元件中運作的 QPainter,並使用它繪出一個表示 currentAngle 值的字串。我們稍後會回到 QPainter,它可以做非常多的事。

t8/main.cpp

 #include "cannonfield.h"

  我們引入了我們新類別的定義。

 class MyWidget : public QWidget
 {
 public:
     MyWidget(QWidget *parent = 0);
 };

  MyWidget 類別將單獨引入一個 LCDRange 與一個 CannonField

     LCDRange *angle = new LCDRange;

  在建構子裡,我們建立並設定了 LCDRange 元件。

     angle->setRange(5, 70);

  我們使 LCDRange 接受 5 到 70 度的角度。

     CannonField *cannonField = new CannonField;

  我們建立了我們的 CannonField 元件。

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

  這裡我們將 LCDRangevalueChanged() signal 連接到 CannonFieldsetAngle() slot。這會使得無論使用者何時操作 LCDRange,都會更新 CannonField 的角度值。我們也做了反向(reverse)連結,因此更改 CannonField 的角度也會更新 LCDRange 的值。在我們的範例中,我們不曾直接改變 CannonField 的角度值。不過藉由最後的 connect(),我們可以肯定:沒有任何改變會打斷這兩個值的同步關係。

  這說明了組件程式設計與適當封裝的威力。

  注意到只有在角度真正發生改變時,才發出 angleChanged() signal 有多麼重要。假如 LCDRangeCannonField 都忽略了這個檢查,在其中一個值首次被改變之後,程式將會進入到一個無窮迴圈(infinite loop)裡。

     QGridLayout *gridLayout = new QGridLayout;

  到目前為止,我們都是使用 QVBoxLayout 來做幾何管理。然而,現在我們想要對版面配置做多一點控制,所以我們使用了更加強大的 QGridLayout 類別。QGridLayout 不是一個元件,它是一個可以管理任何子元件的不同類別。

  我們不需要在 QGridLayout 的建構子裡指定任何尺寸。QGridLayout 將會根據我們擴充的網格決定列數及行數。



  上圖展示了我們試著去實現的版面配置。左圖展示了一個配置概要的畫面;而右圖則是程式的實際擷圖。

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

  我們加入 Quit 按紐在網格的左上角。換言之,就是座標 (0, 0)。

     gridLayout->addWidget(angle, 1, 0);

  我們將代表角度的 LCDRange 放置在 (1, 0)。

     gridLayout->addWidget(cannonField, 1, 1, 2, 1);

  我們使 CannonField 物件佔據 (1, 1) 與 (2, 1)。

     gridLayout->setColumnStretch(1, 10);

  我們告知 QGridLayout 右邊那行(行 1)的伸展係數(stretch factor)為 10,是可伸展的(stretchable)。因為左邊那行不可伸展(根據預設值,它的伸展係數為 0),QGridLayout 將會試著使左邊的元件大小不變,並在 MyWidget 改變大小時,只改變 CannonField 的大小。

  在這個特殊的例子中,行 1 使用任何大於 0 的伸展係數都將有相同的效果。在更加複雜的版面配置中,你可以藉由分配適當的伸展係數,告知一個特定的行或列,是另一行或另一列伸展的兩倍快。

     angle->setValue(60);

  我們設定了一個初始角度值。注意,這將會觸發由 LCDRangeCannonField 的連結。

     angle->setFocus();

  我們的最後一個動作是使 angle 獲得鍵盤焦點(focus),使鍵盤輸入在預設情況下將會傳遞到 LCDRange 元件中。

  LCDRange 並沒有包含任何 keyPressEvent(),所以這看起來並不是非常有用。然而,只需要在它的建構子中加入新的一行:

     setFocusProxy(slider);

  LCDRange 將 slider 設為它的焦點代理者(proxy)。這表示當某個人(程式或是使用者)想要讓 LCDRange 取得鍵盤焦點,slider 就會處理它。而 QSlider 有一個還不錯的鍵盤介面,所以加入了我們給了 LCDRange 的這一行。


Running the Application

  鍵盤現在會做某些事:方向鍵、HomeEndPageUp、和 PageDown 都會做一些合理的事。

  當滾動軸被操作時,CannonField 會顯示新的角度值。若是改變大小,CannonField 會得到盡可能多的空間。


Exercises

  試著改變視窗大小。若是你使它變得非常窄或非常矮,發生了什麼事?

  假如你給了左邊那行一個非 0 的伸展係數。在你改變視窗大小的時候,發生了什麼事?

  不呼叫 QWidget::setFocus()。你更喜歡做什麼行為?

  試著把 "Quit" 改成 "&Quit"。這個按鈕看起來有什麼改變?(是否改變視平台而定。)假如你在程式運作時按下 Alt+Q,發生了什麼事?

  把 CannonField 的文字置中。


來源:Qt Tutorial 8 - Preparing for Battle
版本:4.4.3

【翻譯】Qt Tutorial 7 - One Thing Leads to Another

@
tutorials/tutorial/t7/lcdrange.cpp
tutorials/tutorial/t7/lcdrange.h
tutorials/tutorial/t7/main.cpp
tutorials/tutorial/t7/t7.pro

  這個範例展示了如何建立包含 signal 與 slot 的自訂元件,以及如何用更加複雜的方式將它們連結在一起。首先,原始碼被分割成許多檔案,我們將它們放置在 tutorials/tutorial/t7 資料夾中。




Line by Line Walkthrough

t7/lcdrange.h

  這個檔案主要是抄襲自第六章的 main.cpp。這裡只提及一些比較特別的改變。

 #ifndef LCDRANGE_H
 #define LCDRANGE_H

  這裡以及檔案結尾的 #endif 是標準的 C++ 結構,用以避免標頭檔被多次引入所造成的錯誤。如果你從沒使用過它,這是一個非常好的開發習慣。

 #include <QWidget>

  自從我們的 LCDRange 類別繼承自 QWidget 之後,<QWidget> 就被引入了。一個父類別(parent class)的標頭檔都必須被引入--我們在前面的章節偷了一點懶,我們利用了其他標頭檔來間接引入 <QWidget>

 class QSlider;

  這是另一個經典的手法,不過比較沒那麼常用。因為我們只有在類別的實現中才需要 QSlider,而在類別的介面(interface)中並不需要。所以我們在標頭檔使用類別的前向宣告(forward declaration),並在 .cpp 檔中引入 QSlider 的標頭檔。

  這使得編譯一個大專案(project)更加快速,因為編譯器經常會花上相當多的時間在分析標頭檔,而不是實際上的原始碼。光是這個手法通常就可以讓編譯加速到兩倍或三倍快。

 class LCDRange : public QWidget
 {
     Q_OBJECT

 public:
     LCDRange(QWidget *parent = 0);

  注意這個 Q_OBJECT。這個巨集(macro)必須被包含在所有內含 signal 或 slot 的類別中。假如你有興趣暸解,它定義了一些由 元物件檔(meta-object file) 實現的函數。

     int value() const;

 public slots:
     void setValue(int value);

 signals:
     void valueChanged(int newValue);

  這三個成員構成了這個元件與程式中其他組件之間的介面。直至目前為止,LCDRange 還沒有真的擁有一個 API。

  value() 是一個用以取出 LCDRange 值的公開(public)函式,setValue() 是我們的第一個自訂 slot,且 valueChanged() 是我們的第一個自訂 signal。

  slot 必須照著正規的方式實現(一個 slot 也是一個 C++ 成員函式)。signal 會自動在元物件檔中被實現。signal 遵循著 C++ 保護(protected)函式的存取規則(換言之,它們只可以被定義它的類別,或是繼承自這個類別的類別所發出)。

  當 LCDRange 的值被改變時,valueChanged() 這個 signal 就會被使用。

t7/lcdrange.cpp

  這個檔案主要抄襲自第六章的 main.cpp。這裡只提及一些比較特別的改變。

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

  這些程式碼來自於 LCDRange 建構子。

  第一個 connect() 呼叫與你在先前章節所看過的相同。而第二個 connect() 是新的,它將 slider 的 valueChanged() signal 連接到這個物件的 valueChanged() signal。是的,這是正確的。signal 可以被連接到其他的 signal。當第一個 signal 被發出時,第二個 signal 也會被發出。

  讓我們看看使用者操作這個滾動軸的時候發生了什麼事。這個滾動軸發現它的值被改變,並發出 valueChanged() 的 signal。這個 signal 被連接到 QLCDNumber 的 display() slot 以及 LCDRangevalueChanged() signal。

  於是,當這個 signal 被發出時,LCDRange 發出了它自己的 valueChanged() signal。此外,QLCDNumber::display() 被呼叫並顯示出新的數字。

  注意,你並無法確定任何執行的特定順序,LCDRange::valueChanged() 也許會在 QLCDNumber::display() 被呼叫之前或之後被發出。

 int LCDRange::value() const
 {
     return slider->value();
 }

  value() 的實現相當容易理解。它簡單的返回 slider 的值。

 void LCDRange::setValue(int value)
 {
     slider->setValue(value);
 }

  setValue() 的實現同樣容易理解。注意到因為滾動軸跟 LCD 數字是被連接著的,設定滾動軸的值也會自動更新 LCD 數字。此外,若是滾動軸的值超過了合法範圍,它將會自動調整它的值。

t7/main.cpp

     LCDRange *previousRange = 0;

     for (int row = 0; row < 3; ++row) {
         for (int column = 0; column < 3; ++column) {
             LCDRange *lcdRange = new LCDRange;
             grid->addWidget(lcdRange, row, column);
             if (previousRange)
                 connect(lcdRange, SIGNAL(valueChanged(int)),
                         previousRange, SLOT(setValue(int)));
             previousRange = lcdRange;
         }
     }

  除了 MyWidget 的建構子之外,所有的 main.cpp 都是由前一章複製來的。當我們建立九個 LCDRange 物件時,我們使用 signal 與 slot 機制來連接它們。每一個 valueChanged() signal 都連接到前面一個 LCDRangesetValue() slot。因為每當 LCDRange 的值被改變時,都會發出 valueChanged() signal,所以我們在這裡建立了一條 signal 與 slot 的連鎖反應。


Compiling the Application

  為多個檔案的應用程式建立一個 makefile,與為單一檔案建立一個 makefile 並沒有什麼不同。如果你已經把這個範例的所有檔案存在它專屬的資料夾中,你所需要做的只有:

 qmake -project
 qmake

  第一條指令告知 qmake 創建一個 project(.pro) 檔。第二條指令告知 qmake 根據 .pro 檔去創建一個(跨平臺的) makefile。現在你可以輸入 make(或是 nmake,假如你使用的是 Visual Studio)來建置你的應用程式。


Running the Application

  一開始,這個應用程式的外觀看起來跟前一章一模一樣。試著操作右下角的滾動軸。


Exercises

  使用右下角的滾動軸將所有 LCD 設定為 50。然後點擊第六個滾動軸,將之設定為 30。現在,使用最左邊的滾動軸,將前五個 LCD 設回 50。

  按一下右下角滾動軸把手(handle)的左邊。發生了什麼事?為什麼這是一個正確的行為?


來源:Qt Tutorial 7 - One Thing Leads to Another
版本:4.4.3

【翻譯】Qt Tutorial 6 - Building Blocks Galore!

@
tutorials/tutorial/t6/main.cpp
tutorials/tutorial/t6/t6.pro

  這個範例展示了如何封裝兩個元件成一個新的組件,以及使用許多元件有多麼容易。首先,我們使用一個自訂的元件作為一個子元件。



 #include <QApplication>
 #include <QFont>
 #include <QGridLayout>
 #include <QLCDNumber>
 #include <QPushButton>
 #include <QSlider>
 #include <QVBoxLayout>
 #include <QWidget>

 class LCDRange : public QWidget
 {
 public:
     LCDRange(QWidget *parent = 0);
 };

 LCDRange::LCDRange(QWidget *parent)
     : QWidget(parent)
 {
     QLCDNumber *lcd = new QLCDNumber(2);
     lcd->setSegmentStyle(QLCDNumber::Filled);

     QSlider *slider = new QSlider(Qt::Horizontal);
     slider->setRange(0, 99);
     slider->setValue(0);
     connect(slider, SIGNAL(valueChanged(int)),
             lcd, SLOT(display(int)));

     QVBoxLayout *layout = new QVBoxLayout;
     layout->addWidget(lcd);
     layout->addWidget(slider);
     setLayout(layout);
 }

 class MyWidget : public QWidget
 {
 public:
     MyWidget(QWidget *parent = 0);
 };

 MyWidget::MyWidget(QWidget *parent)
     : QWidget(parent)
 {
     QPushButton *quit = new QPushButton(tr("Quit"));
     quit->setFont(QFont("Times", 18, QFont::Bold));
     connect(quit, SIGNAL(clicked()), qApp, SLOT(quit()));

     QGridLayout *grid = new QGridLayout;
     for (int row = 0; row < 3; ++row) {
         for (int column = 0; column < 3; ++column) {
             LCDRange *lcdRange = new LCDRange;
             grid->addWidget(lcdRange, row, column);
         }
     }

     QVBoxLayout *layout = new QVBoxLayout;
     layout->addWidget(quit);
     layout->addLayout(grid);
     setLayout(layout);
 }

 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);
     MyWidget widget;
     widget.show();
     return app.exec();
 }


Line by Line Walkthrough

 class LCDRange : public QWidget
 {
 public:
     LCDRange(QWidget *parent = 0);
 };

  LCDRange 是一個沒有任何 API 的元件。它只有一個建構子。這種元件不是很有用,所以稍後我們將會加入一些 API。

 LCDRange::LCDRange(QWidget *parent)
     : QWidget(parent)
 {
     QLCDNumber *lcd = new QLCDNumber(2);
     lcd->setSegmentStyle(QLCDNumber::Filled);

     QSlider *slider = new QSlider(Qt::Horizontal);
     slider->setRange(0, 99);
     slider->setValue(0);
     connect(slider, SIGNAL(valueChanged(int)),
             lcd, SLOT(display(int)));

     QVBoxLayout *layout = new QVBoxLayout;
     layout->addWidget(lcd);
     layout->addWidget(slider);
     setLayout(layout);
 }

  這裡直接抄襲了第五章的 MyWidget 建構子。唯一的不同是省略了 Quit 按紐,而且這個類別被重新命名了。

 class MyWidget : public QWidget
 {
 public:
     MyWidget(QWidget *parent = 0);
 };

  MyWidget 同樣除了建構子之外沒有其他 API。

 MyWidget::MyWidget(QWidget *parent)
     : QWidget(parent)
 {
     QPushButton *quit = new QPushButton(tr("Quit"));
     quit->setFont(QFont("Times", 18, QFont::Bold));
     connect(quit, SIGNAL(clicked()), qApp, SLOT(quit()));

  這個過去在改名為 LCDRange 類別裡的按鈕被分隔出來,如此我們便可以有一個 Quit 按紐以及許多 LCDRange 物件。

     QGridLayout *grid = new QGridLayout;

  我們建立了一個將會包含三行(column)的 QGridLayout

  QGridLayout 會自動把它的元件排列到列(row)與行中。你可以在加入元件時指定列數及行數,而 QGridLayout 將會把它們設置在網格(grid)中。

     for (int row = 0; row < 3; ++row) {
         for (int column = 0; column < 3; ++column) {
             LCDRange *lcdRange = new LCDRange;
             grid->addWidget(lcdRange, row, column);
         }
     }

  我們建立了九個 LCDRange 元件,它們全都是網格物件的子元件,我們將它們排列成三行三列。

     QVBoxLayout *layout = new QVBoxLayout;
     layout->addWidget(quit);
     layout->addLayout(grid);
     setLayout(layout);
 }

  最後,我們將 Quit 按紐以及包含 LCDRange 元件的網格版面配置加入到主版面配置中。QWidget::addLayout() 函式類似於 QWidget::addWidget() 函式,使特定的版面配置成為一個主版面配置的子配置。

  就是這樣。


Running the Application

  這支程式顯示了同時使用許多元件有多麼容易。其中每一個元件的行為都像是前一章裡的 LCD 數字與滾動軸。再一次地,它們的不同點在於實現。


Exercises

  在開始時初始化每一個滾動軸為不同 / 隨機的值。


來源:Qt Tutorial 6 - Building Blocks Galore!
版本:4.4.3

【翻譯】Qt Tutorial 5 - Building Blocks

@
tutorials/tutorial/t5/main.cpp
tutorials/tutorial/t5/t5.pro

  這個範例展示了如何建立許多元件,並使用 signal 與 slot 來把它們連接在一起,還有如何處理改變大小。



 #include <QApplication>
 #include <QFont>
 #include <QLCDNumber>
 #include <QPushButton>
 #include <QSlider>
 #include <QVBoxLayout>
 #include <QWidget>

 class MyWidget : public QWidget
 {
 public:
     MyWidget(QWidget *parent = 0);
 };

 MyWidget::MyWidget(QWidget *parent)
     : QWidget(parent)
 {
     QPushButton *quit = new QPushButton(tr("Quit"));
     quit->setFont(QFont("Times", 18, QFont::Bold));

     QLCDNumber *lcd = new QLCDNumber(2);
     lcd->setSegmentStyle(QLCDNumber::Filled);

     QSlider *slider = new QSlider(Qt::Horizontal);
     slider->setRange(0, 99);
     slider->setValue(0);

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

     QVBoxLayout *layout = new QVBoxLayout;
     layout->addWidget(quit);
     layout->addWidget(lcd);
     layout->addWidget(slider);
     setLayout(layout);
 }

 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);
     MyWidget widget;
     widget.show();
     return app.exec();
 }


Line by Line Walkthrough

 class MyWidget : public QWidget
 {
 public:
     MyWidget(QWidget *parent = 0);
 };

 MyWidget::MyWidget(QWidget *parent)
     : QWidget(parent)
 {
     QPushButton *quit = new QPushButton(tr("Quit"));
     quit->setFont(QFont("Times", 18, QFont::Bold));

     QLCDNumber *lcd = new QLCDNumber(2);
     lcd->setSegmentStyle(QLCDNumber::Filled);

  lcd 是一個 QLCDNumber,一個以類似 LCD 方式顯示數字的元件。在這個例子中被設定成顯示兩位數。我們將 QLCDNumber::segmentStyle 的屬性設定為 QLCDNumber::Filled,使這個 LCD 更容易辨識。

  歷史筆記:QLCDNumber 是第一個始終寫在 Qt 裡的元件,那個時候 QPainter 只支持一個原生的繪圖函式:drawLine()。使用 QLCDNumber 來顯示分數的 Tetrix 範例最初版,也是在大約這個時候被寫出來。

     QSlider *slider = new QSlider(Qt::Horizontal);
     slider->setRange(0, 99);
     slider->setValue(0);

  使用者可以使用 QSlider 元件去調整一定範圍內的一個整數值。這裡我們建立了一個水平式的(horizontal) QSlider,設定它的最小值為 0、最大值為 99,且它的初始值為 0。

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

  這裡我們使用了 signal 與 slot 的方式將 slider 的 valueChanged() signal 連接到 LCD 數字的 display() slot。

  無論這個 slider 的值在何時被改變,它都會藉由發出(emit) valueChanged() signal 來傳遞這個新值。因為這個 signal 被連接到 LCD 數字的 display() slot,所以當這個 signal 傳遞時,這個 slot 就會被呼叫。這兩個物件彼此都不知道對方。這就是組件程式設計(component programming)的本質。

  slot 與普通的 C++ 成員函式不同,但遵循著正規的 C++ 存取規則。

     QVBoxLayout *layout = new QVBoxLayout;
     layout->addWidget(quit);
     layout->addWidget(lcd);
     layout->addWidget(slider);
     setLayout(layout);

  MyWidget 現在使用了 QVBoxLayout 來管理其子元件的幾何位置。因此,我們不需要再像第四章一樣,明確指定每一個元件的螢幕座標。此外,使用一個版面配置(layout)確保在視窗改變大小時,其子元件都能跟著改變大小。現在我們使用 QBoxLayout::addWidget()quitlcdslider 加入到版面配置裡。

  QWidget::setLayout() 函式把這個版面配置設置在 MyWidget 中。這使得這個版面配置成為 MyWidget 的一個子元件,所以我們不需要去擔心刪除它的問題;父子關係可以確保它將會跟著 MyWidget 一起被刪除。另外,QWidget::setLayout() 將會自動重新設定版面配置中元件的父元件,使它們成為 MyWidget 的子元件。因此,我們不需要為 quitlcdslider 元件指定 this 為它們的父元件。

  在 Qt 中,元件不是其他元件的子元件(例如 this),就是沒有任何父元件。一個元件可以被加入到一個版面配置中。在這種情況下,版面配置就變成需要負起管理這些元件幾何位置的責任了,不過版面配置可以不必自己去扮演父元件的角色。事實上,QWidget 的建構子需要一個作為其父元件的 QWidget 指標,而且 QLayout 也不是繼承自 QWidget。


Running the Application

  這個 LCD 數字反映了你對滾動軸(slider)所做的一切,而且這個元件也成功的處理了變更大小的動作。注意到當視窗改變大小(因為它可以)時,這個 LCD 數字也會跟著改變大小,不過其他的元件仍維持原樣(不然它們看起來會很奇怪)。


Exercises

  試著改變 LCD 數字,增加更多位數或是改變模式(QLCDNumber::setMode())。你甚至可以增加四個按鈕去設定基數(number base)。

  你也可以改變滾動軸的範圍。

  也許使用 QSpinBox 會比使用滾動軸來的好?

  試著讓應用程式在 LCD 數字溢出(overflow)時退出。


來源:Qt Tutorial 5 - Building Blocks
版本:4.4.3

【翻譯】Qt Tutorial 4 - Let There Be Widgets

@
tutorials/tutorial/t4/main.cpp
tutorials/tutorial/t4/t4.pro

  這個範例展示了如何建立你自己的元件,並且描述了如何控制元件大小的最小值及最大值。



 #include <QApplication>
 #include <QFont>
 #include <QPushButton>
 #include <QWidget>

 class MyWidget : public QWidget
 {
 public:
     MyWidget(QWidget *parent = 0);
 };

 MyWidget::MyWidget(QWidget *parent)
     : QWidget(parent)
 {
     setFixedSize(200, 120);

     QPushButton *quit = new QPushButton(tr("Quit"), this);
     quit->setGeometry(62, 40, 75, 30);
     quit->setFont(QFont("Times", 18, QFont::Bold));

     connect(quit, SIGNAL(clicked()), qApp, SLOT(quit()));
 }

 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);
     MyWidget widget;
     widget.show();
     return app.exec();
 }


Line by Line Walkthrough

 class MyWidget : public QWidget
 {
 public:
     MyWidget(QWidget *parent = 0);
 };

  這裡我們建立了一個新的類別。因為這個類別繼承自 QWidget,所以這個新類別是個元件,並且可以為一個最上層的視窗或是一個子元件(就像是先前章節裡的 QPushButton)。

  這個類別只有一個成員,一個建構子(constructor)(再加上繼承自 QWidget 的成員)。這個建構子是一個標準的 Qt 元件建構子。當你建立元件時,你總是需要包含一個類似的建構子。

  這個引數是它的父元件。為了建立一個最上層視窗,你將父元件指定為一個空指標。就像你所看到的,這個元件預設是作為一個最上層視窗。

 MyWidget::MyWidget(QWidget *parent)

  建構子的實現(implementation)從這裡開始。就像許多元件一樣,它只是把 parent 傳遞給 QWidget 的建構子。

     : QWidget(parent)
 {
     setFixedSize(200, 120);

  因為這個元件並不曉得如何處理改變大小(resize),所以我們固定(fix)了它的大小。在下一個章節,我們將展示一個元件是如何回應使用者的改變大小事件的。

     QPushButton *quit = new QPushButton(tr("Quit"), this);
     quit->setGeometry(62, 40, 75, 30);
     quit->setFont(QFont("Times", 18, QFont::Bold));

  這裡我們建立並設定了一個這個元件的子元件(這個新元件的父元件為 this,在這個例子中即為 MyWidget)。

  tr() 的函式呼叫環繞(around)了 "Quit" 這個字面上的字串,用以標記這個字串是可以翻譯(translation)的,使它能夠在執行期(run-time)根據翻譯檔的內容作改變。在你決定以後要將你的應用程式翻譯為其他語言的情況下,使用 tr() 環繞所有使用者可見的字串是一個好習慣。

  記得這個 quit 是這個建構子裡的一個局部(local)變數,MyWidget 無法追蹤到它。但是 Qt 可以、且將會自動地在 MyWidget 被刪除(delete)時刪除掉它。這就是為什麼 MyWidget 不需要解構子的原因。(另一方面,當你決定要刪除一個子元件時,也沒什麼壞處。這個子元件會自動告知 Qt 它即將死亡。)

  QWidget::setGeometry() 設定了元件的螢幕位置以及大小。這等同於呼叫 QWidget::move() 之後緊接著呼叫 QWidget::resize()。

     connect(quit, SIGNAL(clicked()), qApp, SLOT(quit()));
 }

  qApp 指標是宣告在 <QApplication> 標頭檔裡的一個全域變數。它指向應用程式中的唯一一個 QApplication 實體(instance)。

 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);
     MyWidget widget;
     widget.show();
     return app.exec();
 }

  這裡我們實體化我們的新子元件,顯示它、並且執行這支程式。


Running the Application

  這支程式的行為相當類似前一個章節。不同的地方在於我們實現它的方式。無論如何,它的行為上還是會有點微小差異。試看看改變它的大小吧。


Exercises

  試著在 main() 中建立另一個 MyWidget。發生什麼事?

  試著加入更多按鈕,或是把 QPushButton 以外的東西放在元件裡。


來源:Qt Tutorial 4 - Let There Be Widgets
版本:4.4.3

【翻譯】Qt Tutorial 3 - Family Values

@
tutorials/tutorial/t3/main.cpp
tutorials/tutorial/t3/t3.pro

  這個範例展示了如何建立父元件(parent widget)及子元件(child widget)。



  我們將要保持它的簡單性,只使用一個單獨的父元件及一個獨立的子元件。

 #include <QApplication>
 #include <QFont>
 #include <QPushButton>
 #include <QWidget>

 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);

     QWidget window;
     window.resize(200, 120);

     QPushButton quit("Quit", &window);
     quit.setFont(QFont("Times", 18, QFont::Bold));
     quit.setGeometry(10, 40, 180, 40);
     QObject::connect(&quit, SIGNAL(clicked()), &app, SLOT(quit()));

     window.show();
     return app.exec();
 }


Line by Line Walkthrough

 #include <QWidget>

  我們加入了一行 <QWidget> 的引括,以獲取我們將要使用的基礎元件類別。

     QWidget window;

  我們簡單的建立了一個樸素的元件物件。這個 QWidget 是所有使用者介面物件的基礎。元件是使用者介面的一小部分:其由視窗系統接收滑鼠、鍵盤及其他事件,並自己於螢幕上繪出圖像。一個元件會被其父元件與置於其上的元件所修剪(clip)。

  像是這個特殊的元件,一個沒有被嵌入在父元件中的元件,被稱做一個視窗(window)。通常,視窗都擁有視窗系統所提供的外框及工作列(taskbar)。一個沒有父元件的元件就是一個獨立的視窗。其於螢幕上的最初位置是由視窗系統所控制的。

     window.resize(200, 120);

  我們設定 window 的寬為 200 像素(pixel)、高為 120 像素。

     QPushButton quit("Quit", &window);

  一個孩子(child)誕生了。這個 QPushButton 在建立的同時指定了它的父元件(window)。一個子元件總是顯示在父元件的區域裡面。在顯示的時候,它會被其父元件的邊界修剪。根據預設值,它會被放置在其父類別的左上角,(0, 0)的位置。

     quit.setGeometry(10, 40, 180, 40);

  QWidget::setGeometry() 函式共有四個引數:前面兩個引數是這個按鈕左上角的 x 跟 y 坐標。這個座標是相對於父元件的。而後面兩個引數是這個按鈕的寬度及高度。結果就是這個按鈕從 (10, 40) 延伸到 (190, 80)。

     window.show();

  當父元件被顯示時,它將會呼叫所有子元件的 show 函式(除非它們已經使用 QWidget::hide() 明確指定為隱藏)。


On being mindful of the order of things

  已經學過 QObject 的指導教學讀者們,可以回想當 QObject 的解構子(destructor)被呼叫時,假如這個 QObject 擁有子元件,它的解構子會自動呼叫它每個子元件的解構子。於是,QPushButton quit 的解構子在 main() 結束時似乎被呼叫了兩次。第一次是由於 quit 為其父元件 window 的子元件,所以在 window 離開視野(scope)時,window 的解構子會去刪除它;而第二次則是在 quit 自己離開視野時。不過不需要為這種情況擔心,這個程式碼是正確的。然而,這一個你必須要去留意堆疊(stack)中物件解構子層級(order)的地方。在 note on the order of construction/destruction of QObjects 中可以看到這方面的說明。


Running the Application

  這個按鈕不再充滿整個視窗。相反地,因為呼叫 QWidget::setGeometry() 的緣故,它保持在視窗中 (10, 40) 的位置,且大小為 (180, 40)。


Exercises

  試著改變視窗大小。這個按鈕有什麼改變?若是你在執行這支程式時,使用比較大的字體,這個按鈕的高度怎麼了?如果你試著讓這個視窗變得非常小,又發生什麼事了呢?


來源:Qt Tutorial 3 - Family Values
版本:4.4.3