Qt5 框架的訊號與槽機制簡化了觀察者模式的實作,有效地管理 GUI 元件間的互動,並提升程式碼的可維護性。開發者可以利用 connect 函式將特定訊號連線到對應的槽函式,實作元件間的狀態同步。GTK4 框架則提供另一種跨平台 GUI 開發的途徑,其架構與 Qt5 類別似,但使用 C 語言特性來實作物件導向的設計模式。GTK4 應用程式以事件驅動的方式運作,藉由訊號和回呼函式來處理使用者互動和系統事件。 GTK4 提供豐富的小工具和佈局元件,方便開發者建構使用者介面,並可透過 GtkBuilder 或 Glade 等工具簡化介面設計流程。

觀察者模式在 Qt5 中的實作

觀察者模式是一種行為設計模式,其中一個實體(稱為主題或 Subject)維護著一個依賴列表(稱為觀察者或 Observer),當狀態發生變化時通知這些依賴實體。這種模式在 Qt5 中可以透過訊號和槽(Signals and Slots)來實作。以下是如何在 Qt5 中使用觀察者模式的詳細說明。

主題(Subject)與觀察者(Observer)

  • 主題(Subject):這是包含多個觀察者所感興趣的資訊的實體。當狀態改變時,主題會發出訊號。
  • 觀察者(Observer):這是需要被通知主題變化的實體。觀察者透過槽(Slots)連線到主題的訊號,當訊號被發出時,槽會被呼叫。

訊號(Signal)、槽(Slot)與連線(Connection)

  • 訊號(Signal):當特定事件發生時,訊號會被發出。例如,按鈕按下、值變更或計時器超時都會觸發訊號。
  • 槽(Slot):槽是可以連線到訊號的函式,當訊號被發出時,槽會被呼叫以處理事件或變化。
  • 連線(Connection):使用 QObject::connect() 函式建立訊號與槽之間的連線,當訊號被發出時,連線的槽會被呼叫。

以下是一個使用 Qt5 中觀察者模式的簡單示例程式碼:

#include <QtGui>
#include <QApplication>
#include <QVBoxLayout>
#include <QLabel>
#include <QSpinBox>
#include <QSlider>
#include <QWidget>

int main(int argc, char **argv)
{
    QApplication a(argc, argv);
    QWidget window;
    QVBoxLayout* mainLayout = new QVBoxLayout(&window);
    QLabel* label = new QLabel("0");
    QSpinBox* spinBox = new QSpinBox;
    QSlider* slider = new QSlider(Qt::Horizontal);

    mainLayout->addWidget(label);
    mainLayout->addWidget(spinBox);
    mainLayout->addWidget(slider);

    // 連線 spinBox 的 valueChanged 訊號到 label 的 setNum 槽
    QObject::connect(spinBox, SIGNAL(valueChanged(int)),
                     label, SLOT(setNum(int)));
    // 連線 spinBox 的 valueChanged 訊號到 slider 的 setValue 槽
    QObject::connect(spinBox, SIGNAL(valueChanged(int)),
                     slider, SLOT(setValue(int)));
    // 連線 slider 的 valueChanged 訊號到 label 的 setNum 槽
    QObject::connect(slider, SIGNAL(valueChanged(int)),
                     label, SLOT(setNum(int)));
    // 連線 slider 的 valueChanged 訊號到 spinBox 的 setValue 槽
    QObject::connect(slider, SIGNAL(valueChanged(int)),
                     spinBox, SLOT(setValue(int)));

    window.show();
    return a.exec();
}

內容解密:

此程式碼示範瞭如何在 Qt5 中使用觀察者模式來建立三個小工具之間的互動。以下是詳細解說:

  1. 工具初始化:首先建立了一個應用程式物件 QApplication 和一個主要視窗 QWidget。然後設定了垂直佈局 QVBoxLayout
  2. 工具建立:建立了一個標籤 QLabel、一個旋轉框 QSpinBox 和一個滑動條 QSlider
  3. 工具新增到佈局:將這三個工具新增到視窗的佈局中。
  4. 訊號與槽連線
    • 連線旋轉框的 valueChanged 訊號到標籤的 setNum 槽,使得旋轉框的值變化時標籤顯示相同的數值。
    • 連線旋轉框的 valueChanged 訊號到滑動條的 setValue 槽,使得旋轉框的值變化時滑動條也相應更新。
    • 連線滑動條的 valueChanged 訊號到標籤的 setNum 槽,使得滑動條的值變化時標籤顯示相同的數值。
    • 連線滑動條的 valueChanged 訊號到旋轉框的 setValue 槽,使得滑動條的值變化時旋轉框也相應更新。

這樣設計可以確保這三個工具之間互相同步更新,展示了觀察者模式在 Qt5 中的一種應用。

GTK 框架基礎

GTK4 是一個用於開發 X Window System 應用程式的圖形使用者介面 (GUI) 工具包。與 Qt5 框架類別似,GTK4 支援跨平台開發,可以輕鬆地將 GUI 呈現效果轉移到其他作業系統環境中。

安裝 GTK4

要安裝 GTK4 框架,可以使用以下命令:

$ sudo apt install libgtk-4-dev

GTK4 基礎

GTK4 中的小工具是以層次結構組織的。視窗小工具是主要容器,而其他小工具如按鈕、下拉選單和輸入欄位等則新增到視窗中來構建使用者介面。對於複雜的使用者介面,建議使用 GtkBuilder 和其特定的標記語言來描述介面,而不是手動組裝介面。你也可以使用視覺介面編輯器如 Glade。

GTK4 是事件驅動的,它監聽 X 伺服器上的事件(如滑鼠點選),並將事件通知傳遞給客戶端應用程式。在 Raspberry Pi 上編譯 GTK 程式可以使用以下命令:

gcc $(pkg-config --cflags gtk4) -o example-0 example-0.c $(pkg-config --libs gtk4)

GTK4 請求:無法取得可存取性匯流排位址

執行 GTK4 編譯程式時可能會遇到以下錯誤:

Gtk-WARNING **: Unable to acquire the address of the accessibility bus: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name org.a11y.Bus was not provided by any .service files.

為避免此錯誤,可以使用以下命令來停用可存取性支援:

$ export GTK_ACCESSIBILITY=none

如果需要可存取性支援且因缺少服務而無法運作,可以嘗試安裝或啟用可存取性服務。例如在 Debian/Ubuntu 上安裝 at-spi2-core:

$ sudo apt-get install at-spi2-core

GTK4 範例程式

在 GTK4 框架中,根據訊號應用指的是利用訊號和回撥函式來回應事件和使用者互動的應用。GTK4 提供了一系列圖形控制元素供開發者使用。

這裡有一個簡單的 GTK4 範例程式示範瞭如何處理按鈕點選事件:

#include <gtk/gtk.h>

void on_button_clicked(GtkWidget *widget, gpointer data) {
    g_print("Button clicked!\n");
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);

    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    GtkWidget *button = gtk_button_new_with_label("Click me");
    g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), NULL);

    gtk_container_add(GTK_CONTAINER(window), button);
    gtk_widget_show_all(window);

    gtk_main();
    return 0;
}

內容解密:

此範例程式展示瞭如何建立一個簡單的 GTK4 應用程式並處理按鈕點選事件。以下是詳細解說:

  1. 初始化 GTK:首先初始化 GTK 函式庫。
  2. 建立視窗:建立一個頂層視窗物件 GtkWindow
  3. 設定視窗關閉事件:將視窗的 “destroy” 事件與 gtk_main_quit 函式連線以確保程式能夠正確離開。
  4. 建立按鈕:建立一個帶有標籤 “Click me” 的按鈕物件 GtkButton
  5. 設定按鈕點選事件:將按鈕點選事件與自定義回撥函式 on_button_clicked 聯絡起來。
  6. 將按鈕新增到視窗並顯示所有小工具
  7. 進入主迴圈:進入 GTK 主迴圈以等待事件並處理之。

這樣設計可以確保當按鈕被點選時觸發回撥函式並顯示相應訊息。

GTK4 應用程式開發

在 GTK4 中,當相關訊號被觸發時,GTK4 會呼叫已連線的回呼函式,這使得你可以回應事件。例如,當按鈕被點選時,你可以使用回呼函式來更新標籤的內容。以下是一個簡單的 GTK4 程式範例,展示如何建立一個 200 x 200 畫素的空白視窗。

基本 GTK4 應用程式

首先,我們來看看一個簡單的 GTK4 程式範例。這個程式會建立一個空白的 200 x 200 畫素視窗。

#include <gtk/gtk.h>

static void
activate (GtkApplication* app,
gpointer user_data)
{
    GtkWidget *window;
    window = gtk_application_window_new (app);
    gtk_window_set_title (GTK_WINDOW (window), "Raspberry Pi OS");
    gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
    gtk_widget_show (window);
}

int
main (int argc,
char **argv)
{
    GtkApplication *app;
    int status;
    app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
    g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
    status = g_application_run (G_APPLICATION (app), argc, argv);
    g_object_unref (app);
    return status;
}

這段程式碼會建立一個基本的 GTK4 應用程式。讓我們來解釋一下這段程式碼的主要部分。

檔案名稱及編譯

將上述程式碼儲存為 example-0.c,然後使用以下命令進行編譯:

gcc $(pkg-config --cflags gtk4) -o example-0 example-0.c $(pkg-config --libs gtk4)

這裡 example-0.c 是包含有效 GTK4 指令的 C 原始碼檔案,而 example-0 是輸出的可執行檔名稱。

執行程式

要執行這個程式,請在終端機中輸入以下命令:

$ ./example-0

主要概念解說

  1. main 函式:在 main 函式中,我們建立了一個 GtkApplication 物件。這個物件負責處理應用程式的生命週期和訊號。
  2. 訊號連線:我們使用 g_signal_connectactivate 訊號連線到 activate 函式。當應用程式啟動時,這個訊號會被觸發。
  3. activate 函式:在 activate 函式中,我們建立了一個新的視窗並設定其標題和大小。最後,我們使用 gtk_widget_show 呈現視窗。
  4. 事件處理:當視窗關閉時,g_application_run 函式會傳回一個狀態碼,然後我們釋放 GtkApplication 物件並離開應用程式。

探討 GTK4 的訊號與回呼

GTK4 是根據物件導向原則設計的圖形化介面工具包。雖然這個範例是用 C 語言寫成的,但它仍然展示了 GTK4 的物件導向特性。

  1. C 型別轉換:在 GTK4 中,你經常會看到類別似於 (GtkWindow*) 的轉換。這是 C 語言中的型別轉換,用來改變變數或指標的型別。
  2. GtkApplication:在主函式中,我們建立了一個 GtkApplication 物件。這個物件選擇了一個應用程式識別符(如 "org.gtk.example"),並傳遞給 gtk_application_new 函式。
  3. 訊號連線與處理:我們將 "activate" 訊號連線到 activate 函式。當應用程式啟動時,這個訊號會被觸發並呼叫 activate 函式。
  4. 視窗建立與顯示:在 activate 函式中,我們建立了一個新的 GtkApplicationWindow 並設定其標題和大小。最後,我們使用 gtk_widget_show 顯示視窗。

處理事件與訊號

當程式執行時,GTK4 會接收和處理各種事件,例如滑鼠和鍵盤輸入、來自 Wayfire 或其他應用程式的訊息等。GTK4 會將這些事件轉換為訊號並顯示在小工具中。透過連線這些訊號的處理器,你可以讓你的程式對使用者輸入做出回應。

增加互動性:新增按鈕

互動性是圖形化介面應用程式的一大特點。以下範例展示如何在視窗中新增一個按鈕並設定其標籤。

#include <gtk/gtk.h>

static void
print_rocks (GtkWidget *widget,
gpointer data)
{
    g_print ("Raspberry Pi Rocks\n");
}

static void
activate (GtkApplication *app,
gpointer user_data)
{
    GtkWidget *window;
    GtkWidget *button;
    GtkWidget *box;

    window = gtk_application_window_new (app);
    gtk_window_set_title (GTK_WINDOW (window), "Window");
    gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);

    box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_widget_set_halign (box, GTK_ALIGN_CENTER);
    gtk_widget_set_valign (box, GTK_ALIGN_CENTER);

    button = gtk_button_new_with_label ("Raspberry Pi Rocks");
    g_signal_connect(button, "clicked", G_CALLBACK(print_rocks), NULL);

    gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 5);

    gtk_container_add(GTK_CONTAINER(window), box);
    gtk_widget_show_all(window);
}

int
main(int argc, char **argv)
{
    GtkApplication *app;
    int status;

    app = gtk_application_new("org.gtk.example", G_APPLICATION_FLAGS_NONE);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);

    status = g_application_run(G_APPLICATION(app), argc, argv);

    g_object_unref(app);
    return status;
}

內容解密:

  1. print_rocks 函式:這是一個回呼函式,當按鈕被點選時會被呼叫。它會在終端機中列印「Raspberry Pi Rocks」。
  2. activate 函式:在這個函式中,我們建立了一個新的視窗並設定其標題和大小。然後我們建立了一個垂直方向的盒子(box),並在其中新增一個按鈕。
  3. 按鈕建立與連線:我們使用 gtk_button_new_with_label 建立了一個帶有標籤「Raspberry Pi Rocks」的按鈕。然後我們將其「clicked」訊號連線到 print_rocks 函式。
  4. 佈局管理:我們使用 gtk_box_pack_start 把按鈕新增到盒子中,並設定適當的間距。
  5. 顯示所有小工具:最後,我們使用 gtk_widget_show_all 一次顯示所有小工具。