2月 04, 2009

【翻譯】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

1 回覆:

Unknown 提到...

你好,我想請教幾個問題
1. 在lcdrange.h裡頭的 value()有什麼作用,我反覆看了幾次code還是不知其解,到底是在哪裡用到呢?
2. 在lcdrange.h裡頭的 valueChanged有什麼作用,在其.cpp檔裡面也沒有繼續詳述,那在.h裡面寫出來到底有什麼意義?
3. 在man裡頭的 if(previousRange),這又是什麼樣的寫法?
懇請回答,謝謝!

張貼留言