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
| 特性 / Feature | GTK (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 |
| GBoxed | g_boxed_copy() / g_boxed_free() |
| gchar* | g_strdup() / g_free() |
| GList | g_list_free() |
| GHashTable | g_hash_table_destroy() |
| GVariant | g_variant_unref() |
| GError | g_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 handleGErrorfrom 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 控件 →