1. 程式人生 > >Qt核心機制與原理之訊號與槽

Qt核心機制與原理之訊號與槽

訊號與槽

    訊號和槽機制是Qt的核心機制之一,要掌握Qt程式設計就需要對訊號和槽有所瞭解。訊號和槽是一種高階介面,它們被應用於物件之間的通訊,它們是Qt的核心特性,也是Qt不同於其它同類工具包的重要地方之一。
在我們所瞭解的其它GUI工具包中,視窗小部件(widget)都有一個回撥函式用於響應它們觸發的動作,這個回撥函式通常是一個指向某個函式的指標。在Qt中用訊號和槽取代了上述機制。

1.訊號(signal)

當物件的狀態發生改變時,訊號被某一個物件發射(emit)。只有定義過這個訊號的類或者其派生類能夠發射這個訊號。當一個訊號被髮射時,與其相關聯的槽將被執行,就象一個正常的函式呼叫一樣。訊號-槽機制獨立於任何GUI事件迴圈。只有當所有的槽正確返回以後,發射函式(emit)才返回。

如果存在多個槽與某個訊號相關聯,那麼,當這個訊號被髮射時,這些槽將會一個接一個地被執行,但是它們執行的順序將會是不確定的,並且我們不能指定它們執行的順序。

訊號的宣告是在標頭檔案中進行的,並且moc工具會注意不要將訊號定義在實現檔案中。Qt用signals關鍵字標識訊號宣告區,隨後即可宣告自己的訊號。
 注意,訊號和槽函式的宣告一般位於標頭檔案中,同時在類宣告的開始位置必須加上Q_OBJECT語句,這條語句是不可缺少的,它將告訴編譯器在編譯之前必須先應用moc工具進行擴充套件。關鍵字signals指出隨後開始訊號的宣告,這裡signals用的是複數形式而非單數,siganls沒有public、private、protected等屬性,這點不同於slots。另外,signals、slots關鍵字是QT自己定義的,不是C++中的關鍵字。

還有,訊號的宣告類似於函式的宣告而非變數的宣告,左邊要有型別,右邊要有括號,如果要向槽中傳遞引數的話,在括號中指定每個形式引數的型別,當然,形式引數的個數可以多於一個。

從形式上講,訊號的宣告與普通的C++函式是一樣的,但是訊號沒有定義函式實現。另外,訊號的返回型別都是void,而C++函式的返回值可以有豐富的型別。

注意,signal 程式碼會由 moc 自動生成,moc將其轉化為標準的C++語句,C++前處理器會認為自己處理的是標準C++原始檔。所以大家不要在自己的C++實現檔案實現signal。
程式碼widgt.h訊號模組
     //訊號宣告區
signals:
    /*
     * 當某個訊號對其客戶或所有者發生的內部狀態發生改變,
     * 訊號被一個物件發射。只有 定義過這個訊號的類及其派生類能夠發射這個訊號。
     * 當一個訊號被髮射時,與其相關聯的槽將被立刻執行,
     * 就象一個正常的函式呼叫一樣。訊號-槽機制完全獨立於任何GUI事件迴圈。
     * 只有當所有的槽返回以後發射函式(emit)才返回。
     */

    //宣告訊號mySignal()
    void mySignal();
    //宣告訊號mySignal(int)
    void mySignal(int
x); //宣告訊號mySignalParam(int,int) void mySignalParam(int x,int y);
2.槽(slot)

槽是普通的C++成員函式,可以被正常呼叫,不同之處是它們可以與訊號(signal)相關聯。當與其關聯的訊號被髮射時,這個槽就會被呼叫。槽可以有引數,但槽的引數不能有預設值。

槽也和普通成員函式一樣有訪問許可權。槽的訪問許可權決定了誰可以和它相連。通常,槽也分為三種類型,即public slots、private slots和protected slots。

public slots:在這個程式碼區段內宣告的槽意味著任何物件都可將訊號與之相連線。這對於元件程式設計來說非常有用:你生成了許多物件,它們互相併不知道,把它們的訊號和槽連線起來,這樣資訊就可以正確地傳遞,並且就像一個小孩子喜歡玩耍的鐵路軌道上的火車模型,把它開啟然後讓它跑起來。

protected slots:在這個程式碼區段內宣告的槽意味著當前類及其子類可以將訊號與之相關聯。這些槽只是類的實現的一部分,而不是它和外界的介面。

private slots:在這個程式碼區段內宣告的槽意味著只有類自己可以將訊號與之相關聯。這就是說這些槽和這個類是非常緊密的,甚至它的子類都沒有獲得連線權利這樣的信任。

通常,我們使用public和private宣告槽是比較常見的,建議儘量不要使用protected關鍵字來修飾槽的屬性。此外,槽也能夠宣告為虛擬函式。

槽的宣告也是在標頭檔案中進行的。
程式碼槽函式模組 widget.h
   //槽宣告區
public slots:
    /*槽是普通的C++成員函式,可以被正常呼叫,它們唯一的特殊性就是很多訊號可以與其相關聯。
     * 當與其關聯的訊號被髮射時,這個槽就會被呼叫。槽可以有引數,但槽的引數不能有預設值。
既然槽是普通的成員函式,因此與其它的函式一樣,它們也有存取許可權。
槽的存取許可權決定了誰能夠與其相關聯。同普通的C++成員函式一樣,
槽函式也分為三種類型,即public slots、private slots和protected slots。
public slots:在這個區內宣告的槽意味著任何物件都可將訊號與之相連線。
這對於元件程式設計非常有用,你可以建立彼此互不瞭解的物件,
將它們的訊號與槽進行連線以便資訊能夠正確的傳遞。
protected slots:在這個區內宣告的槽意味著當前類及其子類可以將訊號與之相連線。
這適用於那些槽,它們是類實現的一部分,但是其介面介面卻面向外部。
private slots:在這個區內宣告的槽意味著只有類自己可以將訊號與之相連線。
這適用於聯絡非常緊密的類。
槽也能夠宣告為虛擬函式,這也是非常有用的。
    */
    //宣告槽函式mySlot()
    void mySlot();
    //宣告槽函式mySlot(int)
    void mySlot(int x);
    //宣告槽函式mySignalParam (int,int)
    void mySlotParam(int x,int y);
一個訊號對應一個槽函式
程式碼widget.h 全部
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QMessageBox>
namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    void slotFileNew();
    //訊號宣告區
signals:
    /*
     * 當某個訊號對其客戶或所有者發生的內部狀態發生改變,
     * 訊號被一個物件發射。只有 定義過這個訊號的類及其派生類能夠發射這個訊號。
     * 當一個訊號被髮射時,與其相關聯的槽將被立刻執行,
     * 就象一個正常的函式呼叫一樣。訊號-槽機制完全獨立於任何GUI事件迴圈。
     * 只有當所有的槽返回以後發射函式(emit)才返回。
     */

    //宣告訊號mySignal()
    void mySignal();
    //宣告訊號mySignal(int)
    void mySignal(int x);
    //宣告訊號mySignalParam(int,int)
    void mySignalParam(int x,int y);
    //槽宣告區
public slots:
    /*槽是普通的C++成員函式,可以被正常呼叫,它們唯一的特殊性就是很多訊號可以與其相關聯。
     * 當與其關聯的訊號被髮射時,這個槽就會被呼叫。槽可以有引數,但槽的引數不能有預設值。
既然槽是普通的成員函式,因此與其它的函式一樣,它們也有存取許可權。
槽的存取許可權決定了誰能夠與其相關聯。同普通的C++成員函式一樣,
槽函式也分為三種類型,即public slots、private slots和protected slots。
public slots:在這個區內宣告的槽意味著任何物件都可將訊號與之相連線。
這對於元件程式設計非常有用,你可以建立彼此互不瞭解的物件,
將它們的訊號與槽進行連線以便資訊能夠正確的傳遞。
protected slots:在這個區內宣告的槽意味著當前類及其子類可以將訊號與之相連線。
這適用於那些槽,它們是類實現的一部分,但是其介面介面卻面向外部。
private slots:在這個區內宣告的槽意味著只有類自己可以將訊號與之相連線。
這適用於聯絡非常緊密的類。
槽也能夠宣告為虛擬函式,這也是非常有用的。
    */
    //宣告槽函式mySlot()
    void mySlot();
    //宣告槽函式mySlot(int)
    void mySlot(int x);
    //宣告槽函式mySignalParam (int,int)
    void mySlotParam(int x,int y);

private slots:

private:
    Ui::Widget *ui;
    QString input="0";
};

#endif // WIDGET_H
/*訊號  補充
 * 如果存在多個槽與某個訊號相關聯,那麼,當這個訊號被髮射時,
 * 這些槽將會一個接一個地 執行,
 * 但是它們執行的順序將會是隨機的、不確定的,
 * 我們不能人為地指定哪個先執行、哪 個後執行。
 *
 * 從形式上 講訊號的宣告與普通的C++函式是一樣的,
 * 但是訊號卻沒有函式體定義,另外,訊號的返回 型別都是void,
 * 不要指望能從訊號返回什麼有用資訊。
訊號由moc自動產生,它們不應該在.cpp檔案中實現。
moc 全稱是 Meta-Object Compiler,也就是“元物件編譯器”  其實就是預處理
只有用到訊號signals和槽slots時才會用到MOC,
因為採用訊號signals和槽slots是QT的特性,
而C++沒有,所以採用了MOC(元物件編譯器)把訊號signals和槽slots部分編譯成C++語言.
用訊號signals和槽slots需注意的基本問題是:
1)在類class宣告中必須加入Q_OBJECT;
2)在CPP檔案中要把訊號signals和槽slots聯絡起來,
即使用connect,例connect( iv, SIGNAL(clicked (QIconViewItem *)), this, SLOT( draw()));
 * */
3.訊號與槽的關聯

槽和普通的C++成員函式幾乎是一樣的-可以是虛擬函式;可以被過載;可以是共有的、保護的或是私有的,並且也可以被其它C++成員函式直接呼叫;還有,它們的引數可以是任意型別。唯一不同的是:槽還可以和訊號連線在一起,在這種情況下,每當發射這個訊號的時候,就會自動呼叫這個槽。
定義widget.cpp
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
   //將訊號mySignal()與槽mySlot()相關聯
    connect(this,SIGNAL(mySignal()),SLOT(mySlot()));
    //將訊號mySignal(int)與槽mySlot(int)相關聯
    connect(this,SIGNAL(mySignal(int)),SLOT(mySlot(int)));
    //將訊號mySignalParam(int,int)與槽mySlotParam(int,int)相關聯
    connect(this,SIGNAL(mySignalParam(int,int)),SLOT(mySlotParam(int,int)));
}
 //static void about(QWidget *parent, const QString &title, const QString &text);
// 定義槽函式mySlot()
void Widget::mySlot()
{
    QMessageBox::about(this,"訊號類", "這是一個沒有引數的訊號/槽.");
}

// 定義槽函式mySlot(int)
void Widget::mySlot(int x)
{
    QMessageBox::about(this,"訊號類", "這是有一個引數的訊號/槽.");
}

// 定義槽函式mySlotParam(int,int)
void Widget::mySlotParam(int x,int y)
{
    char s[256];
    sprintf(s,"x:%d y:%d",x,y);
    QMessageBox::about(this,"訊號類", s);
}
void Widget::slotFileNew()
{

    //發射訊號mySignal(int)
    emit mySignal(5);
    //發射訊號mySignalParam(5,100)
    emit mySignalParam(5,100);
    //發射訊號mySignal()
    emit mySignal();
}

Widget::~Widget()
{
    delete ui;
}

最後補上 widget.ui程式碼
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Widget</class>
 <widget class="QWidget" name="Widget">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Widget</string>
  </property>
  <widget class="QLabel" name="result">
   <property name="geometry">
    <rect>
     <x>40</x>
     <y>10</y>
     <width>121</width>
     <height>41</height>
    </rect>
   </property>
   <property name="text">
    <string>TextLabel</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btn_one">
   <property name="geometry">
    <rect>
     <x>280</x>
     <y>80</y>
     <width>89</width>
     <height>25</height>
    </rect>
   </property>
   <property name="text">
    <string>1</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btn_two">
   <property name="geometry">
    <rect>
     <x>280</x>
     <y>120</y>
     <width>89</width>
     <height>25</height>
    </rect>
   </property>
   <property name="text">
    <string>2</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btn_three">
   <property name="geometry">
    <rect>
     <x>280</x>
     <y>160</y>
     <width>89</width>
     <height>25</height>
    </rect>
   </property>
   <property name="text">
    <string>3</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btn_four">
   <property name="geometry">
    <rect>
     <x>280</x>
     <y>200</y>
     <width>89</width>
     <height>25</height>
    </rect>
   </property>
   <property name="text">
    <string>4</string>
   </property>
  </widget>
  <widget class="QPushButton" name="close">
   <property name="geometry">
    <rect>
     <x>280</x>
     <y>240</y>
     <width>89</width>
     <height>25</height>
    </rect>
   </property>
   <property name="text">
    <string>close</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btn_three_2">
   <property name="geometry">
    <rect>
     <x>180</x>
     <y>160</y>
     <width>89</width>
     <height>25</height>
    </rect>
   </property>
   <property name="text">
    <string>3</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btn_two_2">
   <property name="geometry">
    <rect>
     <x>180</x>
     <y>120</y>
     <width>89</width>
     <height>25</height>
    </rect>
   </property>
   <property name="text">
    <string>2</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btn_four_2">
   <property name="geometry">
    <rect>
     <x>180</x>
     <y>200</y>
     <width>89</width>
     <height>25</height>
    </rect>
   </property>
   <property name="text">
    <string>4</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btn_one_2">
   <property name="geometry">
    <rect>
     <x>180</x>
     <y>80</y>
     <width>89</width>
     <height>25</height>
    </rect>
   </property>
   <property name="text">
    <string>1</string>
   </property>
  </widget>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>
程式碼貼完了,我們再來張效果圖

這裡寫圖片描述
需要注意的問題
訊號與槽機制是比較靈活的,但有些侷限性我們必須瞭解,這樣在實際的使用過程中才能夠做到有的放矢,避免產生一些錯誤。下面就介紹一下這方面的情況。

⑴ 訊號與槽的效率是非常高的,但是同真正的回撥函式比較起來,由於增加了靈活性,因此在速度上還是有所損失,當然這種損失相對來說是比較小的,通過在一臺i586-133的機器上測試是10微秒(執行Linux),可見這種機制所提供的簡潔性、靈活性還是值得的。但如果我們要追求高效率的話,比如在實時系統中就要儘可能的少用這種機制。

⑵ 訊號與槽機制與普通函式的呼叫一樣,如果使用不當的話,在程式執行時也有可能產生死迴圈。因此,在定義槽函式時一定要注意避免間接形成無限迴圈,即在槽中再次發射所接收到的同樣訊號。

⑶ 如果一個訊號與多個槽相關聯的話,那麼,當這個訊號被髮射時,與之相關的槽被啟用的順序將是隨機的,並且我們不能指定該順序。

⑷ 巨集定義不能用在signal和slot的引數中。

⑸ 建構函式不能用在signals或者slots宣告區域內。

⑹ 函式指標不能作為訊號或槽的引數。

⑺ 訊號與槽不能有預設引數。

⑻ 訊號與槽也不能攜帶模板類引數。

通過上面程式碼 我們瞭解了訊號與槽可以傳遞引數等 我們看一下下面的案例
此處 直接貼程式碼
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
#include <QTextEdit>
#include <QPlainTextEdit>
#include <QDebug>
namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    void lineEditText(QString);
private:
    Ui::MainWindow *ui;

signals:
    void signalo11(int n=0);//object1-1的訊號
    void signalo12(int n=0);//object1-2的訊號
    void signalo21(int n=0);//object2-1的訊號
    void signalo22(int n=0);//object2-2的訊號
    void signalo31(int n=0);//object3-1的訊號
    void signalo32(int n=0);//object3-2的訊號
private slots:

    void slotso22(int n=0);//object2-2的槽
    void slotso31(int n=0);//object3-1的槽
    void on_Object1_clicked();
    void on_Object2_clicked();
    void on_Object3_clicked();
};

#endif // MAINWINDOW_H

.h標頭檔案給出了訊號與槽,那麼我們去把槽與訊號定義連線起來
具體還是看程式碼:MainWindows.cpp

#include "MainWindow.h"
#include "ui_MainWindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //此處用this,是一個窗體,如果不同的窗體,可以用不同的 物件
#if 0
    //1.一個訊號可以與另一個訊號相連
     connect(this,SIGNAL(signalo11(int)),this,SIGNAL(signalo21(int)));
     connect(this,SIGNAL(signalo21(int)),this,SLOT(slotso22(int)));
#endif
#if 0
    //2.表示一個訊號可以與多個槽相連
    connect(this,SIGNAL(signalo12(int)),this,SLOT(slotso22(int)));
    connect(this,SIGNAL(signalo12(int)),this,SLOT(slotso31(int)));
#endif
   #if 1
    //3.表示同一個槽可以響應多個訊號
     connect(this,SIGNAL(signalo12(int)),this,SLOT(slotso22(int)));
     connect(this,SIGNAL(signalo32(int)),this,SLOT(slotso22(int)));
   #endif
}


    void MainWindow::slotso22(int n)//object2-2的槽
    {
        if(n==0)
        {
             ui->lineEdit->setText("1.我是Object1點選 object2-2槽出現的");
        }
        else if(n==1)
        {
             ui->lineEdit->setText("2.我是Object1點選 object2-2槽出現的");
        }
        else if(n==2)
        {
            qDebug()<<"click Object1";
             ui->lineEdit->setText("3.我是Object1點選 object2-2槽出現的");
        }  else if(n==3)
        {
            qDebug()<<"click Object3";
             ui->lineEdit->setText("3.我是Object3點選 object2-2槽出現的");
        }else
        {
             ui->lineEdit->setText("我是Object1點選 object2-1槽出現的");
        }
    }
    void MainWindow::slotso31(int n)//object3-1的槽
    {
         ui->textEdit->setText("2.我是Object1點選 object3-1槽出現的");
    }


MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_Object1_clicked()
{
#if 0
    emit signalo11(0);
#endif
#if 0//2
    emit signalo12(1);
#endif
#if 1//3
    emit signalo12(2);
#endif
}

void MainWindow::on_Object2_clicked()
{
      ui->lineEdit->setText("我是Object2點選按鈕 出現的");
}

void MainWindow::on_Object3_clicked()
{
    emit signalo32(3);
}

最後補充上ui
MainWindows.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>522</width>
    <height>460</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="Object1">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>30</y>
      <width>113</width>
      <height>32</height>
     </rect>
    </property>
    <property name="text">
     <string>Object1</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="lineEdit">
    <property name="geometry">
     <rect>
      <x>40</x>
      <y>90</y>
      <width>371</width>
      <height>51</height>
     </rect>
    </property>
    <property name="text">
     <string>單行文字輸入,一般用於使用者名稱、密碼等少量文字互動地方</string>
    </property>
   </widget>
   <widget class="QLabel" name="label">
    <property name="geometry">
     <rect>
      <x>40</x>
      <y>70</y>
      <width>411</width>
      <height>16</height>
     </rect>
    </property>
    <property name="text">
     <string>我是LineEdit</string>
    </property>
   </widget>
   <widget class="QLabel" name="label_2">
    <property name="geometry">
     <rect>
      <x>40</x>
      <y>10</y>
      <width>81</width>
      <height>16</height>
     </rect>
    </property>
    <property name="text">
     <string>我是button</string>
    </property>
   </widget>
   <widget class="QTextEdit" name="textEdit">
    <property name="geometry">
     <rect>
      <x>40</x>
      <y>170</y>
      <width>371</width>
      <height>91</height>
     </rect>
    </property>
    <property name="html">
     <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'.SF NS Text'; font-size:13pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;多行文字,也可以顯示HTML格式文字&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
    </property>
   </widget>
   <widget class="QLabel" name="label_3">
    <property name="geometry">
     <rect>
      <x>40</x>
      <y>150</y>
      <width>101</width>
      <height>16</height>
     </rect>
    </property>
    <property name="text">
     <string>我是textEdit</string>
    </property>
   </widget>
   <widget class="QPlainTextEdit" name="plainTextEdit">
    <property name="geometry">
     <rect>
      <x>40</x>
      <y>300</y>
      <width>371</width>
      <height>91</height>
     </rect>
    </property>
    <property name="plainText">
     <string>QTextEdit很像,但它多用於需要與文字進行處理的地方,而QTextEdit多用於顯示,可以說,QPlainTextEdit對於plain text處理能力比QTextEdit強</string>
    </property>
   </widget>
   <widget class="QLabel" name="label_4">
    <property name="geometry">
     <rect>
      <x>40</x>
      <y>270</y>
      <width>101</width>
      <height>21</height>
     </rect>
    </property>
    <property name="text">
     <string>我是QTextEdit</string>
    </property>
   </widget>
   <widget class="QPushButton" name="Object2">
    <property name="geometry">
     <rect>
      <x>190</x>
      <