Qt入門(18)——使用訊號和槽連線控制元件

尹成發表於2014-09-30

下面顯示瞭如何使用訊號和槽來建立自定義視窗部件,和如何使用更加複雜的方式把它們連線起來。

首先,原始檔被我們分成幾部分並放在放在t7目錄下。


t7/lcdrange.h包含LCDRange類定義。
t7/lcdrange.cpp包含LCDRange類實現。
t7/main.cpp包含MyWidget和main。


t7/lcdrange.h這個檔案主要利用了前面博文的main.cpp,在這裡只是說明一下改變了哪些。

    #ifndef LCDRANGE_H
    #define LCDRANGE_H
這裡是一個經典的C語句,為了避免出現一個標頭檔案被包含不止一次的情況。如果你沒有使用過它,這是開發中的一個很好的習慣。#ifndef需要把這個標頭檔案的全部都包含進去。

    #include <qvbox.h>
qvbox.h被包含了。LCDRange繼承了QVBox,所以父類的標頭檔案必須被包含。我們在前幾章裡面偷了一點懶,我們通過包含其它一些標頭檔案,比如qpushbutton.h,這樣就可以間接地包含qwidget.h。

    class QSlider;
這裡是另外一個小伎倆,但是沒有前一個用的多。因為我們在類的介面中不需要QSlider,僅僅是在實現中,我們在標頭檔案中使用一個前置的類宣告,並且在.cpp檔案中包含一個QSlider的標頭檔案。

這會使編譯一個大的專案變得更快,因為當一個標頭檔案改變的時候,很少的檔案需要重新編譯。它通常可以給大型編譯加速兩倍或兩倍以上。

    class LCDRange : public QVBox
    {
        Q_OBJECT
    public:
        LCDRange( QWidget *parent=0, const char *name=0 );
        int value() const;
    public slots:
        void setValue( int );
    signals:
        void valueChanged( int );


meta object file. 注意Q_OBJECT。這個巨集必須被包含到所有使用訊號和/或槽的類。如果你很好奇,它定義了在元物件檔案中實現的一些函式。


這三個成員函式構成了這個視窗部件和程式中其它元件的介面。直到現在,LCDRange根本沒有一個真正的介面。

value()是一個可以訪問LCDRange的值的公共函式。setValue()是我們第一個自定義槽,並且valueChanged()是我們第一個自定義訊號。

槽必須按通常的方式實現(記住槽也是一個C++成員函式)。訊號可以在元物件檔案中自動實現。訊號也遵守C++函式的保護法則(比如,一個類只能發射它自己定義的或者繼承來的訊號)。

當LCDRange的值發生變化時,valueChanged()訊號就會被使用——你從這個名字中就可以猜到。這將不會是你將會看到的命名為somethingChanged()的最後一個訊號。

t7/lcdrange.cpp這個檔案主要利用了t6/main.cpp,在這裡只是說明一下改變了哪些。

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



這個程式碼來自LCDRange的建構函式。


第一個connect和你在上一篇中看到的一樣。第二個是新的,它把滑塊的valueChanged()訊號和這個物件的valueChanged訊號連線起來了。帶有三個引數的connect()函式連線到this物件的訊號或槽。


是的,這是正確的。訊號可以被連線到其它的訊號。當第一個訊號被髮射時,第二個訊號也被髮射。


讓我們來看看當使用者操作這個滑塊的時候都發生了些什麼。滑塊看到自己的值發生了改變,併發射了valueChanged()訊號。這個訊號被連線到QLCDNumber的display()槽和LCDRange的valueChanged()訊號。


所以,當這個訊號被髮射的時候,LCDRange發射它自己的valueChanged()訊號。另外,QLCDNumber::display()被呼叫並顯示新的數字。


注意你並沒有保證執行的任何順序——LCDRange::valueChanged()也許在QLCDNumber::display()之前或者之後發射,這是完全任意的。


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


value()的實現是直接了當的,它簡單地返回滑塊的值。

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



setValue()的實現是相當直接了當的。注意因為滑塊和LCD數字是連線的,設定滑塊的值就會自動的改變LCD數字的值。另外,如果滑塊的值超過了合法範圍,它會自動調節。

        LCDRange *previous = 0;
        for( int r = 0 ; r < 4 ; r++ ) {
            for( int c = 0 ; c < 4 ; c++ ) {
                LCDRange* lr = new LCDRange( grid );
                if ( previous )
                    connect( lr, SIGNAL(valueChanged(int)),
                             previous, SLOT(setValue(int)) );
                previous = lr;
            }
        }



main.cpp中所有的部分都是上一章複製的,除了MyWidget的建構函式。當我們建立16個RCDRange物件時,我們現在使用訊號/槽機制連線它們。每一個的valueChanged()訊號都和前一個的setValue()槽連線起來了。因為當LCDRange的值發生改變的時候,發射一個valueChanged()訊號(驚奇!),我們在這裡建立了一個訊號和槽的“鏈”。


編譯
為一個多檔案的應用程式建立一個makefile和為一個單檔案的應用程式建立一個makefile是沒有什麼不同的。如果你已經把這個例子中的所有檔案都儲存到它們自己的目錄中,你所要做的就是這些:


qmake -project
qmake
第一個命令呼叫qmake來生成一個.pro(專案)檔案。第二個命令根據這個專案檔案來生成一個(系統相關的)makefile。你現在可以輸入make(或者nmake,如果你使用Visual Studio)。

相關文章