Signals 和 Slots

  • 何謂 signals 和 slots?
    1. Qt 信號處理的機制。
    2. GUI 應用程式利用 signals 和 slots 來回應使用者輸入。
    3. 在 Qt 中,signals 和 slots 為巨集關鍵字。
  • Signal/slot 與 widget?
    1. Widget 為選單、工具列、按鈕、輸入窗等 GUI 元件。
    2. 當使用者與 widget 互動時,widget 會發出一個 signal。
    3. 將 signal 連結到一個回呼函式 slot。
    4. 執行回呼函式 slot 指定動作。
  • 類別使用 signals 和 slots 成員函式的限制:
    1. 必須繼承自 QObject 類別(class)。
    2. 一定要使用 Q_OBJECT 巨集(macro),即 Q_OBJECT 必須出現在類別定義中。
    3. signals 和 slots 的參數不可使用函式指標。
  • 類別使用 signals 和 slots 成員函式:
    1. 編輯類別定義 MyWindow.h。
      # MyWindow 繼承類別 QMainWindow,提供應用程式的主要視窗功能。
      # 若需要一個對話窗,要繼承 QDialog。
      class MyWindow : public QMainWindow
      {
        Q_OBJECT
        public:
          MyWindow();
          virtual ~MyWindow();
        signals:
          void A_Signal();
      # signals A_Signal() 沒指定參數
        private slots:
          void doSomething();
      # slots doSomething() 沒指定參數
      }
      

    2. 呼叫 emit 發出 A_Signal() 信號:
      emit A_Signal();
      
    3. 透過 QObject 類別的 connect 成員函式將 slots 連結到信號。
      bool QObject::connect (const QObject * sender, const char * signal,
                             const QObject * receiver, const char * member)
      # connect 函式要傳入擁有信號的物件(傳送者)、信號函式、擁有 slot 的物件(接收者)及 slot 名稱。
      
      connect (button, SIGNAL(clicked()), this, SLOT(doSomething()));
      # this 在此代表 MyWindow
      
    4. 實作 slot
      void MyWindow::doSomething()
      {
        // Slot code
      }
      

  • 實例:做一個簡單的按鈕,它可以有一個標籤和位元圖示,使用者可以透過滑鼠或鍵盤來點選它。
    1. 編輯 ButtonWindow.h 宣告類別:
      #include <qmainwindow.h>
      class ButtonWindow : public QMainWindow
      {
        Q_OBJECT
        public:
          ButtonWindow(QWidget *parent = 0, const char *name = 0);
          virtual ~ButtonWindow();
        private slots:
          void Clicked();
      };
      
    2. 編輯 ButtonWindow 建構函式
      ButtonWindow::ButtonWindow(QWidget *parent, const char *name)
               : QMainWindow(parent, name)
      {
      # setCaption 是 QMainWindow 的成員函式,可設定視窗標題。
        this->setCaption("This is the window Title");
      # 產生按鈕、將按鈕的 clicked 信號連結到 Clicked() slot 中。
        QPushButton *button = new QPushButton("Click Me!", this, "Button1");
      # 設定按鈕的幾何大小。
        button->setGeometry(50,30,70,20);
      # 將按鈕的 clicked signal 連結到 Clicked() slot 中。
        connect (button, SIGNAL(clicked()), this, SLOT(Clicked())); 
      }
      
    3. 編輯 ~ButtonWindow 解構函式
      ButtonWindow::~ButtonWindow()
      {
      # Qt 自動管理 widget 的解構工作,所以解構函式是空的。
      }
      
    4. 編輯 slot Clicked():
      void ButtonWindow::Clicked(void)
      {
        std::cout << "clicked!\n";
      }
      
    5. 編輯 main 程式 ButtonWindow.cpp
      #include  "ButtonWindow.moc"
      #include  <qpushbutton.h>
      #include  <qapplication.h>
      #include  <iostream>
      
      int main(int argc, char **argv)
      {
        QApplication app(argc,argv);
      # 產生一個 ButtonWindow 的物件,設定應用程式的主視窗,並將視窗顯示在螢幕上。
        ButtonWindow *window = new ButtonWindow();
      # 設定應用程式的主視窗。
        app.setMainWidget(window);
      # 將視窗顯示在螢幕上。
        window->show();
        return app.exec();
      }
      
    6. 編譯前,執行前置處理器 moc:
      $ moc ButtonWindow.h -o ButtonWindow.moc
      
      # Qt 的 MOC( Meta-Object System )
      ## 標準的 C++ 無法提供 signal/slot 連結所需 之 meta 訊息。
      ## 標頭檔若包含 \verb|Q_OBJECT| 巨集,MOC 會解析巨集定義,
      ## 並產生 Qt meta-object 相關的 C++ 程式碼。
      ## 使用 qmake 產生 Makefile,就會包括 moc 的使用。
      
    7. 編譯程式,並連結 moc 的結果:
      $ g++ -o button ButtonWindow.cpp -I$QTDIR/include -L$QTDIR/lib -lqt-mt
      
    8. 執行程式:
      Image button

  • QPushButton 建構函式說明:
    1. QPushButton 的建構函式:
      QPushButton::QPushButton(const QString &text, QWidget *parent,
                                     const char* name=0 )
      
      1. 第一個參數是按鈕的文字標籤,
      2. 隨後是它的父親 widget,
      3. 最後就是 Qt 內部認定的按鈕名稱。
    2. parent 參數
      1. 在 Qwidget 很常見,父親 widget 可以控制何時顯示或破壞它,包含不同的屬性。
      2. 如果在 parent 參數傳入 NULL,表示這個 widget 是最上層的 widget,而且產生一個空白視窗來包含它。
      3. 範例中用 this 來代表 ButtonWindow 物件,按鈕就會加到 ButtonWindow 的主要區域。
    3. name 參數設定 Qt 內部使用的 widget 名稱。如果 Qt 遇到一個錯誤,這個 widget 名稱就會被印在錯誤訊息上,所以最好輸入適切的 widget 名稱,方便往後除錯。
    4. setGeometry 決定絕對位置,但很少用,因為它無法隨視窗自動調整大小。
  • 類別 QLayout 與 box widget
    1. 利用 QLayout 或 box widget ,給定區域和 widget 之間的空間之後,它會自動調整。
    2. QLayout 類別和 box widget 之間的關鍵性差異就是 layout 物件不是 widget。
    3. layout 類別從 QObject 衍伸而來。
    4. Box widget(即 QHBox 和 QVBox)衍伸自 QWidget,可以把它當成一般的 widget 來處理。
    5. QLayout 有自動調整大小的優點,而如果 widget 要改變大小時,必須人工呼叫 QWidget::resizeEvent()。
  • QVBoxLayout 建構函式說明:
    1. QVBoxLayout 建構函式(QHBoxLayout 也擁有相同的 API)。
      # QLayout 的 parent 參數,可以是 widget 或其他的 QLayout。
      QVBoxLayout::QVBoxLayout (QWidget *parent, int margin,
                                   int spacing, const char *name)
      QVBoxLayout::QVBoxLayout (QLayout *parentLayout, int spacing,
                                   const char * name)
      QVBoxLayout::QVBoxLayout (int spacing, const char *name)
      
    2. 若沒指定 parent,只能藉由成員函式 addLayout 加到其他 QLayout 中。
      QBoxLayout::addWidget (QWidget *widget, int stretch = 0,
                                 int alignment = 0 )
      QBoxLayout::addLayout (QLayout *layout, int stretch = 0)
      
    3. margin 和 spacing 的單位為像素(pixel),分別用來指定 QLayout 外圍和 widget 之間的空白空間。

  • 實例:使用 QBoxLayout 類別,設計三個 QLabel,讓視窗大小改變時, label 自動被放大或縮小來符合可用的空間。
    1. 程式的標頭檔案 LayoutWindow.h。
      #include <qmainwindow.h>
      class LayoutWindow : public QMainWindow
      {
        Q_OBJECT
        public:
          LayoutWindow(QWidget *parent = 0, const char *name = 0);
          virtual ~LayoutWindow();
      };
      
    2. 程式 LayoutWindow.cpp。
      #include <qapplication.h>
      #include <qlabel.h>
      #include <qlayout.h>
      #include "LayoutWindow.moc"
      LayoutWindow::LayoutWindow(QWidget *parent, const char *name) :
      QMainWindow(parent, name)
      {
        this->setCaption("Layouts");
      
      # 因為不能直接將 QLayout 加到 QMainWindow,所以產生一個假的 QWidget。
        QWidget *widget = new QWidget(this);
        setCentralWidget(widget);
        QHBoxLayout *horizontal = new QHBoxLayout(widget, 5, 10,
                                    “horizontal”);
        QVBoxLayout *vertical = new QVBoxLayout();
        QLabel* label1 = new QLabel("Top", widget, "textLabel1" );
        QLabel* label2 = new QLabel("Bottom", widget, "textLabel2");
        QLabel* label3 = new QLabel("Right", widget, "textLabel3");
        vertical->addWidget(label1);
        vertical->addWidget(label2);
        horizontal->addLayout(vertical);
        horizontal->addWidget(label3);
        resize( 150, 100 );
      }
      
      LayoutWindow::~LayoutWindow()
      {
      }
      
      int main(int argc, char **argv)
      {
        QApplication app(argc,argv);
        LayoutWindow *window = new LayoutWindow();
        app.setMainWidget(window);
        window->show();
        return app.exec();
      }
      
    3. 編譯前,在標頭檔案上執行 moc:
      $ moc LayoutWindow.h -o LayoutWindow.moc
      $ g++ -o layout LayoutWindow.cpp -I$QTDIR/include -L$QTDIR/lib -lqt-mt
      
    4. 執行程式:
      Image layout

  DYWANG_HOME