用 VS Code 搞 Qt6:讓訊號和槽自動建立連線

2022-11-06 15:06:57

Qt 具備讓某個物件的訊號與符合要求的槽函數自動建立連線。弄起來也很簡單,只要呼叫這個靜態方法即可:

QMetaObject::connectSlotsByName(...);

connectSlotsByName 方法需要一個引數,此引數的指標指向一個範例,這個範例自身的訊號,以及它的子級物件的訊號都會自動連線。

不過,在用的時候要注意以下幾點,否則 connectSlotsByName 方法是不起作用的。

1、如果類是從某個 QObject 類派生的,比如常見的 QWidget 類,在類的宣告中一個定要加上 Q_OBJECT 宏。這條老周在上一篇中說過,不加這個訊號和槽不能建立連線。

2、物件一定要有 Name,即用 setObjectName 方法設定。雖然物件可以使用重複的名字,但不建議這樣做,因為 connectSlotsByName 方法只要找到一個名字匹配的物件,就會停止查詢。所以,就算你設定了 10 個名為「myButton」 的物件,結果也只能有一個會自動繫結訊號和槽,其他的同名物件會忽略。

3、一定要在所有物件都初始化完畢,包括呼叫 setObjectName 方法設定物件名稱後呼叫 connectSlotsByName 方法。這樣才會有效。

setObjectName 方法用起來很簡單,只要傳遞物件的名字即可,字串型別。名字你可以隨便取。例如

QLabel lb ...
lb.setObjectName("bug");

這時候,標籤物件的名字是「bug」。

Slot 要支援被自動連線,函數(方法)也是有嚴格的命名規則的。你必須按照這個規則來,否則不會被識別。槽函數命名規則如下:

on_XXX_SSS

1、以「on」開頭,每一節用下劃線連起來。

2、XXX 是物件名,注意是物件名,就是用 setObjectName 方法設定的名稱,不是你程式碼中定義的變數名。這個得注意,不能搞錯了。

3、SSS 是訊號。

比如,按鈕的 clicked 訊號,你讓要自己寫的槽能夠被自動連線,就得這樣命名槽函數:on_mybtn_clicked。其中,「mybtn」是物件名。

------------------------------------------- 銀河分隔線 ------------------------------------------

下面咱們來動手做個例子,就好理解了。

一、先弄好 CMake 檔案。

cmake_minimum_required(VERSION 3.0.0)
project(TestApp VERSION 0.1.0)

find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_AUTOMOC YES)

add_executable(TestApp WIN32 main.cpp app.h app.cpp)
target_link_libraries(TestApp PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)

這個範例有三個檔案。main.cpp 是寫 main 函數的地方。app.h 和 app.cpp 中是咱們自定義的視窗類——從 QWidget 類派生。

二、定義 MyWindow 類,基礎類別是 QWidget。

/** app.h **/
#include <QMetaObject>
#include <QWidget>
#include <QApplication>
#include <QObject>
#include <QLabel>
#include <QPushButton>
#include <QMessageBox>
#include <QVBoxLayout>
#include <QHBoxLayout>

class MyWindow : public QWidget 
{
    // 這個宏很容易忘了,忘了就不能連線訊號和槽了
    Q_OBJECT

public:
    // 建構函式
    MyWindow(QWidget* parent = nullptr);
    // 解構函式
    ~MyWindow();

private:
    // 私有函數
    void initUi(void);
    // 以下是用到的部件(控制元件)
    QPushButton *btn1;
    QPushButton *btn2;
    QLabel *lb;
    // 佈局
    QVBoxLayout *layout;
    QHBoxLayout *sublayout;

    // 這幾個函數是用於自動繫結的槽
private slots:
    void on_b1_clicked();
    void on_b2_clicked();
};

所有 QObject 的子類,想使用 Signal 和 Slot ,必須呼叫 Q_OBJECT 宏。這裡有兩個按鈕,on_b1_clicked 和 on_b2_clicked 都是槽。要讓兩個按鈕自動連線,必須分別設定它的 object name 為 「b1」 和 「b2」。

三、下面是 initUi 函數的實現程式碼,用於初始化表單。

void MyWindow::initUi()
{
    // 按鈕1
    btn1 = new QPushButton(this);
    // 設定按鈕1的文字
    btn1 -> setText("左邊");
    // 重要:給它個名字
    btn1 -> setObjectName("b1");

    // 按鈕2
    btn2 = new QPushButton(this);
    // 設定按鈕2的文字
    btn2 -> setText("右邊");
    // 重要:設定名稱
    btn2 -> setObjectName("b2");

    // 標籤
    lb = new QLabel("請點選下面的按鈕", this);

    // 佈局
    layout = new QVBoxLayout(this);
    layout -> addWidget(lb, 0, Qt::AlignTop);
    sublayout = new QHBoxLayout(this);
    // 新增要佈局的元件
    sublayout -> addWidget(btn1);
    sublayout -> addWidget(btn2);
    layout->addLayout(sublayout);

    // 視窗
    this -> setWindowTitle("範例王");
    this -> resize(240, 100);
}

呼叫按鈕物件的 setObjectName 方法就可以為其分配名稱。注意在呼叫 QPushButton 類別建構函式時,要把當前視窗的指標傳遞給 parent 引數,使用按鈕成為 MyWindow 的子級物件。這樣後面才能做訊號與槽的自動連線。

四、在 MyWindow 類建構函式中,先呼叫 initUi ,再呼叫 connectSlotsByName 靜態方法。

MyWindow::MyWindow(QWidget *parent)
    : QWidget::QWidget(parent)
{
    // 呼叫以下函數,初始化UI
    initUi();
    // 一定要在所有東東都初始化完畢後呼叫才有效
    QMetaObject::connectSlotsByName(this);
}

五、下面是兩個槽函數的實現。功能簡單,用 QMessageBox 顯示彈出框。

void MyWindow::on_b1_clicked()
{
    QMessageBox::information(this, "好訊息", "左轉是男廁", QMessageBox::Ok);
}
void MyWindow::on_b2_clicked()
{
    QMessageBox::information(this, "好訊息", "右轉是女廁", QMessageBox::Ok);
}

六、在 main.cpp 中寫 main 函數。

#include "app.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    // 範例化視窗
    MyWindow wind;
    // 顯示視窗
    wind.show();
    // 訊息迴圈
    return app.exec();
}

 

執行結果如下面超清動畫所示。

從結果可以看到,名為「b1」的按鈕自動將 clicked 訊號連線到 on_b1_clicked 函數;名為「b2」的按鈕自動將 clicked 訊號連線到 on_b2_clicked 函數。

好了,今天的主題咱們就聊到這兒了。