强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Qt 与 GTK 图形框架教程 / 07 - GTK 基础 / GTK Basics

GTK 基础 / GTK Basics

掌握 GTK 的基础:GObject 类型系统、信号机制、属性系统和内存管理。 Master GTK fundamentals: GObject type system, signals, properties, and memory management.


7.1 GObject 类型系统 / GObject Type System

GObject 是 GTK 的对象基础,在纯 C 语言上实现面向对象编程。 GObject is GTK’s object foundation, implementing OOP in pure C.

核心概念 / Core Concepts

概念 / Concept说明 / Description
GType运行时类型标识符 / Runtime type identifier
GObject所有对象的基类 / Base class for all objects
GTypeClass类结构(类似 C++ vtable) / Class structure
GTypeInstance实例结构 / Instance structure
GInterface接口(类似 Java interface) / Interface

GObject 继承层次 / GObject Hierarchy

GType (类型系统)
└── GInitiallyUnowned
    └── GObject              ← 所有对象的基类
        ├── GtkWidget         ← 所有控件基类
        │   ├── GtkWindow
        │   ├── GtkButton
        │   ├── GtkLabel
        │   └── GtkBox
        ├── GtkApplication    ← 应用程序
        ├── GtkBuilder        ← UI 构建器
        └── GtkSettings       ← 全局设置

7.2 定义自定义 GObject / Defining Custom GObject

C 语言完整示例 / Complete C Example

/* mycounter.h */
#ifndef MY_COUNTER_H
#define MY_COUNTER_H

#include <gtk/gtk.h>

G_BEGIN_DECLS

/* 类型声明宏 */
#define MY_TYPE_COUNTER (my_counter_get_type())
G_DECLARE_DERIVABLE_TYPE(MyCounter, my_counter, MY, COUNTER, GObject)

/* 类结构(虚函数表) */
struct _MyCounterClass {
    GObjectClass parent_class;

    /* 虚函数(子类可重写) */
    void (*count_changed)(MyCounter *self, int new_count);

    /* 保留指针 */
    gpointer padding[4];
};

/* 公共 API */
MyCounter *my_counter_new(void);
int my_counter_get_count(MyCounter *self);
void my_counter_increment(MyCounter *self);
void my_counter_decrement(MyCounter *self);
void my_counter_reset(MyCounter *self);

G_END_DECLS
#endif /* MY_COUNTER_H */
/* mycounter.c */
#include "mycounter.h"

/* 私有数据结构 */
typedef struct {
    int count;
} MyCounterPrivate;

G_DEFINE_TYPE_WITH_PRIVATE(MyCounter, my_counter, G_TYPE_OBJECT)

/* 枚举属性 */
enum {
    PROP_0,
    PROP_COUNT,
    N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES] = { NULL };

/* 枚举信号 */
enum {
    SIGNAL_COUNT_CHANGED,
    N_SIGNALS
};

static guint signals[N_SIGNALS] = { 0 };

/* ============================================================
 * 属性系统实现
 * ============================================================ */
static void my_counter_set_property(GObject *object, guint prop_id,
                                     const GValue *value, GParamSpec *pspec)
{
    MyCounter *self = MY_COUNTER(object);
    MyCounterPrivate *priv = my_counter_get_instance_private(self);

    switch (prop_id) {
    case PROP_COUNT:
        priv->count = g_value_get_int(value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    }
}

static void my_counter_get_property(GObject *object, guint prop_id,
                                     GValue *value, GParamSpec *pspec)
{
    MyCounter *self = MY_COUNTER(object);
    MyCounterPrivate *priv = my_counter_get_instance_private(self);

    switch (prop_id) {
    case PROP_COUNT:
        g_value_set_int(value, priv->count);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    }
}

/* ============================================================
 * 类初始化 / Class Initialization
 * ============================================================ */
static void my_counter_class_init(MyCounterClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    object_class->set_property = my_counter_set_property;
    object_class->get_property = my_counter_get_property;

    /* 注册属性 */
    properties[PROP_COUNT] =
        g_param_spec_int("count", "Count", "Current count value",
                         G_MININT, G_MAXINT, 0,
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

    g_object_class_install_properties(object_class, N_PROPERTIES, properties);

    /* 注册信号 */
    signals[SIGNAL_COUNT_CHANGED] =
        g_signal_new("count-changed",
                     G_TYPE_FROM_CLASS(klass),
                     G_SIGNAL_RUN_LAST,
                     G_STRUCT_OFFSET(MyCounterClass, count_changed),
                     NULL, NULL, NULL,
                     G_TYPE_NONE, 1, G_TYPE_INT);
}

/* ============================================================
 * 实例初始化 / Instance Initialization
 * ============================================================ */
static void my_counter_init(MyCounter *self)
{
    MyCounterPrivate *priv = my_counter_get_instance_private(self);
    priv->count = 0;
}

/* ============================================================
 * 公共 API / Public API
 * ============================================================ */
MyCounter *my_counter_new(void)
{
    return g_object_new(MY_TYPE_COUNTER, NULL);
}

int my_counter_get_count(MyCounter *self)
{
    g_return_val_if_fail(MY_IS_COUNTER(self), 0);
    MyCounterPrivate *priv = my_counter_get_instance_private(self);
    return priv->count;
}

void my_counter_increment(MyCounter *self)
{
    g_return_if_fail(MY_IS_COUNTER(self));
    MyCounterPrivate *priv = my_counter_get_instance_private(self);
    priv->count++;
    g_signal_emit(self, signals[SIGNAL_COUNT_CHANGED], 0, priv->count);
}

void my_counter_decrement(MyCounter *self)
{
    g_return_if_fail(MY_IS_COUNTER(self));
    MyCounterPrivate *priv = my_counter_get_instance_private(self);
    priv->count--;
    g_signal_emit(self, signals[SIGNAL_COUNT_CHANGED], 0, priv->count);
}

void my_counter_reset(MyCounter *self)
{
    g_return_if_fail(MY_IS_COUNTER(self));
    MyCounterPrivate *priv = my_counter_get_instance_private(self);
    priv->count = 0;
    g_signal_emit(self, signals[SIGNAL_COUNT_CHANGED], 0, priv->count);
}

应用程序入口 / Application Entry Point

/* main.c */
#include <gtk/gtk.h>
#include "mycounter.h"

/* 信号回调 */
static void on_count_changed(MyCounter *counter, int new_count, gpointer data)
{
    GtkLabel *label = GTK_LABEL(data);
    char *text = g_strdup_printf("计数: %d", new_count);
    gtk_label_set_text(label, text);
    g_free(text);
}

/* 激活回调 */
static void on_activate(GtkApplication *app, gpointer user_data)
{
    /* 创建主窗口 */
    GtkWidget *window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "GTK 基础示例");
    gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);

    /* 创建垂直布局 */
    GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
    gtk_widget_set_margin_start(box, 20);
    gtk_widget_set_margin_end(box, 20);
    gtk_widget_set_margin_top(box, 20);
    gtk_widget_set_margin_bottom(box, 20);
    gtk_window_set_child(GTK_WINDOW(window), box);

    /* 标题标签 */
    GtkWidget *title = gtk_label_new("GObject 信号演示");
    gtk_widget_add_css_class(title, "title-2");
    gtk_box_append(GTK_BOX(box), title);

    /* 计数显示标签 */
    GtkWidget *count_label = gtk_label_new("计数: 0");
    gtk_widget_add_css_class(count_label, "title-1");
    gtk_box_append(GTK_BOX(box), count_label);

    /* 创建计数器 */
    MyCounter *counter = my_counter_new();
    g_signal_connect(counter, "count-changed",
                     G_CALLBACK(on_count_changed), count_label);

    /* 按钮容器 */
    GtkWidget *btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
    gtk_widget_set_halign(btn_box, GTK_ALIGN_CENTER);
    gtk_box_append(GTK_BOX(box), btn_box);

    /* 减少按钮 */
    GtkWidget *btn_dec = gtk_button_new_with_label("− 减少");
    g_signal_connect_swapped(btn_dec, "clicked",
                              G_CALLBACK(my_counter_decrement), counter);
    gtk_box_append(GTK_BOX(btn_box), btn_dec);

    /* 重置按钮 */
    GtkWidget *btn_reset = gtk_button_new_with_label("重置");
    g_signal_connect_swapped(btn_reset, "clicked",
                              G_CALLBACK(my_counter_reset), counter);
    gtk_box_append(GTK_BOX(btn_box), btn_reset);

    /* 增加按钮 */
    GtkWidget *btn_inc = gtk_button_new_with_label("+ 增加");
    g_signal_connect_swapped(btn_inc, "clicked",
                              G_CALLBACK(my_counter_increment), counter);
    gtk_box_append(GTK_BOX(btn_box), btn_inc);

    /* 显示窗口 */
    gtk_window_present(GTK_WINDOW(window));
}

int main(int argc, char *argv[])
{
    GtkApplication *app = gtk_application_new(
        "com.example.gtk_basics", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);
    return status;
}

编译命令 / Build Command

# 使用 gcc
gcc -o myapp main.c mycounter.c \
    $(pkg-config --cflags --libs gtk4) \
    -Wall -Wextra

# 使用 Meson (推荐)
# meson.build
project('gtk-basics', 'c',
  version: '1.0',
  default_options: ['warning_level=2'])

gtk4_dep = dependency('gtk4')

executable('myapp',
  'main.c',
  'mycounter.c',
  dependencies: gtk4_dep)

Python 示例 (PyGObject)

#!/usr/bin/env python3
"""GTK 基础示例 - PyGObject"""

import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Gtk, Adw, GObject


class Counter(GObject.Object):
    """自定义 GObject 子类"""
    __gtype_name__ = "Counter"

    # 定义信号
    __gsignals__ = {
        "count-changed": (GObject.SignalFlags.RUN_LAST, None, (int,)),
    }

    def __init__(self):
        super().__init__()
        self._count = 0

    @GObject.Property(type=int, default=0)
    def count(self) -> int:
        return self._count

    @count.setter
    def count(self, value: int):
        if self._count != value:
            self._count = value
            self.emit("count-changed", value)
            self.notify("count")

    def increment(self):
        self.count = self._count + 1

    def decrement(self):
        self.count = self._count - 1

    def reset(self):
        self.count = 0


class MyApp(Adw.Application):
    def __init__(self):
        super().__init__(application_id="com.example.gtk_basics")
        self.connect("activate", self.on_activate)

    def on_activate(self, app):
        # 创建窗口
        window = Adw.ApplicationWindow(application=app)
        window.set_title("GTK 基础示例")
        window.set_default_size(300, 200)

        # 主容器
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        main_box.set_margin_start(20)
        main_box.set_margin_end(20)
        main_box.set_margin_top(20)
        main_box.set_margin_bottom(20)
        window.set_content(main_box)

        # 标题
        title = Gtk.Label(label="GObject 信号演示")
        title.add_css_class("title-2")
        main_box.append(title)

        # 计数标签
        count_label = Gtk.Label(label="计数: 0")
        count_label.add_css_class("title-1")
        main_box.append(count_label)

        # 计数器
        counter = Counter()
        counter.connect("count-changed",
                        lambda obj, val: count_label.set_text(f"计数: {val}"))

        # 按钮容器
        btn_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        btn_box.set_halign(Gtk.Align.CENTER)
        main_box.append(btn_box)

        # 减少按钮
        btn_dec = Gtk.Button(label="− 减少")
        btn_dec.connect("clicked", lambda _: counter.decrement())
        btn_box.append(btn_dec)

        # 重置按钮
        btn_reset = Gtk.Button(label="重置")
        btn_reset.connect("clicked", lambda _: counter.reset())
        btn_box.append(btn_reset)

        # 增加按钮
        btn_inc = Gtk.Button(label="+ 增加")
        btn_inc.connect("clicked", lambda _: counter.increment())
        btn_box.append(btn_inc)

        window.present()


if __name__ == "__main__":
    app = MyApp()
    app.run()

7.3 信号机制 / Signal Mechanism

GTK 信号 vs Qt 信号对比 / GTK Signals vs Qt Signals

特性 / FeatureGTK (GObject)Qt
定义方式g_signal_new()signals: 关键字
连接方式g_signal_connect()QObject::connect()
类型检查运行时 / Runtime编译期 / Compile-time
参数传递GValue 包装原生 C++ 类型
断开连接g_signal_handler_disconnect()QObject::disconnect()
阻塞g_signal_handler_block()QObject::blockSignals()

信号连接方式 / Signal Connection Methods

/* GTK 信号连接方式 */

/* 1. 标准连接 / Standard connection */
g_signal_connect(button, "clicked",
                 G_CALLBACK(on_button_clicked), user_data);

/* 2. 连接到 GObject 属性变化 */
g_signal_connect(entry, "notify::text",
                 G_CALLBACK(on_text_changed), NULL);

/* 3. 连接后获取 handler_id,用于断开 */
gulong handler_id = g_signal_connect(
    object, "signal-name", G_CALLBACK(callback), data);

/* 4. 断开连接 */
g_signal_handler_disconnect(object, handler_id);

/* 5. 阻塞信号 */
g_signal_handler_block(object, handler_id);
g_signal_handler_unblock(object, handler_id);

/* 6. 连接并立即断开(一次性)*/
g_signal_connect(button, "clicked",
                 G_CALLBACK(on_clicked_once), NULL);
// 在回调中返回 TRUE 表示阻止后续处理器

7.4 属性系统 / Property System

GObject 属性 / GObject Properties

/* 注册属性 */
enum {
    PROP_NAME = 1,
    PROP_AGE,
    N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES] = { NULL };

/* 在 class_init 中 */
properties[PROP_NAME] =
    g_param_spec_string("name", "Name", "The person's name",
                        "Unknown",
                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

properties[PROP_AGE] =
    g_param_spec_uint("age", "Age", "The person's age",
                      0, 200, 0,
                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

g_object_class_install_properties(object_class, N_PROPERTIES, properties);

/* 使用属性 / Using properties */
g_object_set(person, "name", "张三", "age", 30, NULL);

gchar *name;
guint age;
g_object_get(person, "name", &name, "age", &age, NULL);
g_print("Name: %s, Age: %u\n", name, age);
g_free(name);

/* 绑定属性 / Property binding */
g_object_bind_property(source, "name",
                       target, "label",
                       G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);

7.5 内存管理 / Memory Management

引用计数 / Reference Counting

/* GObject 引用计数 */

/* 增加引用 */
g_object_ref(object);

/* 减少引用(引用为0时自动释放) */
g_object_unref(object);

/* GObject 始终由创建者管理引用 */
MyWidget *widget = g_object_new(MY_TYPE_WIDGET, NULL);
// 使用完毕后
g_object_unref(widget);

/* GtkWindow 等控件由 GTK 管理,不需要手动 unref */
/* GTK widgets are managed by GTK, no manual unref needed */

/* sink 浮动引用 / Sink floating reference */
// GObject 创建时有浮动引用(floating)
// 当第一次加入容器时,浮动引用被 sink
// gtk_window_new() 返回浮动引用的对象
// gtk_window_present() 会 sink 它

/* GBoxed 类型管理 */
gchar *str = g_strdup("Hello");
g_free(str);  // 手动释放

GList *list = g_list_append(NULL, data);
g_list_free(list);  // 手动释放列表

内存管理规则 / Memory Management Rules

类型 / Type管理方式 / Management
GObject引用计数 / Reference counting
GtkWidget父容器管理 / Parent container manages
GBoxedg_boxed_copy() / g_boxed_free()
gchar*g_strdup() / g_free()
GListg_list_free()
GHashTableg_hash_table_destroy()
GVariantg_variant_unref()
GErrorg_error_free()

7.6 GType 类型系统 / GType System

类型注册 / Type Registration

/* GType 类型注册方式 */

/* 1. 使用宏自动注册(推荐) */
G_DEFINE_TYPE(MyWidget, my_widget, GTK_TYPE_WIDGET)

/* 2. 带私有数据 */
G_DEFINE_TYPE_WITH_PRIVATE(MyWidget, my_widget, GTK_TYPE_WIDGET)

/* 3. 接口实现 */
G_DEFINE_TYPE_WITH_CODE(MyWidget, my_widget, GTK_TYPE_WIDGET,
    G_IMPLEMENT_INTERFACE(GTK_TYPE_BUILDABLE, my_widget_buildable_init))

/* 4. 检查类型 */
if (GTK_IS_BUTTON(widget)) {
    GtkButton *button = GTK_BUTTON(widget);
    // 使用 button...
}

/* 5. 运行时类型信息 */
GType type = G_TYPE_FROM_INSTANCE(widget);
const gchar *name = g_type_name(type);
g_print("Type: %s\n", name);

/* 6. 类型继承检查 */
if (g_type_is_a(type, GTK_TYPE_WIDGET)) {
    g_print("Is a GtkWidget\n");
}

注意事项 / Important Notes

⚠️ 类型转换宏 / Type Casting Macros

始终使用 GTK 提供的类型转换宏(如 GTK_BUTTON()),不要使用 C 强制转换。 宏会在运行时检查类型安全。

Always use GTK’s type casting macros (like GTK_BUTTON()). They check type safety at runtime.

⚠️ GError 处理 / GError Handling

许多 GLib 函数使用 GError **error 参数。始终检查并处理错误。 Always check and handle GError from GLib functions.

GError *error = NULL;
if (!gtk_widget_activate_action(widget, "win.close", NULL, &error)) {
    g_printerr("Error: %s\n", error->message);
    g_error_free(error);
}

⚠️ GTK4 API 变化 / GTK4 API Changes

GTK4 移除了 gtk_widget_show_all(),所有控件默认可见。 使用 gtk_window_present() 替代 gtk_widget_show()

GTK4 removed gtk_widget_show_all(). All widgets are visible by default.


扩展阅读 / Further Reading

资源 / Resource链接 / Link
GObject 参考https://docs.gtk.org/gobject/
GLib 参考https://docs.gtk.org/glib/
GTK4 文档https://docs.gtk.org/gtk4/
GObject 教程https://developer.gnome.org/documentation/tutorials/basics.html
GTK4 示例https://gitlab.gnome.org/GNOME/gtk/-/tree/main/examples

06 - Qt 数据库 | 08 - GTK4 控件