Qt 与 GTK 图形框架教程 / 08 - GTK4 控件 / GTK4 Widgets
GTK4 控件 / GTK4 Widgets
全面掌握 GTK4 控件体系:布局容器、列表、树视图、弹窗和 CSS 样式。 Master GTK4 widgets: layout containers, lists, tree views, popups, and CSS styling.
8.1 GTK4 控件总览 / GTK4 Widget Overview
控件分类 / Widget Categories
| 类别 / Category | 控件 / Widgets | 说明 / Description |
|---|---|---|
| 窗口 | GtkWindow, GtkApplicationWindow, GtkDialog | 应用窗口容器 |
| 显示 | GtkLabel, GtkImage, GtkPicture | 文本与图片 |
| 按钮 | GtkButton, GtkToggleButton, GtkCheckButton | 各类按钮 |
| 输入 | GtkEntry, GtkTextView, GtkSpinButton, GtkDropDown | 文本与数值输入 |
| 容器 | GtkBox, GtkGrid, GtkStack, GtkNotebook | 布局容器 |
| 列表 | GtkListView, GtkGridView, GtkColumnView | 数据视图 |
| 弹窗 | GtkPopover, GtkMessageDialog, GtkAlertDialog | 弹出式 UI |
| 导航 | GtkHeaderBar, GtkActionBar, GtkSidebar | 导航栏 |
| 滚动 | GtkScrolledWindow, GtkScrollbar | 滚动容器 |
GTK4 vs GTK3 主要区别 / GTK4 vs GTK3 Key Differences
| 变化 / Change | GTK3 | GTK4 |
|---|---|---|
| 控件可见性 | gtk_widget_show_all() | 默认可见 / Visible by default |
| 渲染引擎 | GDK + Cairo | GSK (GPU 加速) |
| 事件处理 | GtkEventBox | 直接处理 / Direct on widgets |
| 布局管理 | GtkContainer | GtkLayoutManager |
| CSS 节点 | 有限支持 | 完整 CSS 节点树 |
| 列表控件 | GtkTreeView | GtkListView + GListModel |
| 拖放 | GtkDragSource | GtkDragSource / GtkDropTarget |
| 手势 | 无 | GtkGesture* 系列 |
8.2 布局容器 / Layout Containers
GtkBox - 线性布局
/* GtkBox 水平布局 */
GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_set_margin_start(hbox, 10);
gtk_widget_set_margin_end(hbox, 10);
gtk_widget_set_margin_top(hbox, 10);
gtk_widget_set_margin_bottom(hbox, 10);
/* 添加控件 */
GtkWidget *label = gtk_label_new("姓名:");
GtkWidget *entry = gtk_entry_new();
GtkWidget *button = gtk_button_new_with_label("确定");
gtk_box_append(GTK_BOX(hbox), label);
gtk_box_append(GTK_BOX(hbox), entry);
gtk_box_append(GTK_BOX(hbox), button);
/* 设置扩展属性 */
gtk_widget_set_hexpand(entry, TRUE); // entry 占据剩余空间
GtkGrid - 网格布局
/* GtkGrid 网格布局 */
GtkWidget *grid = gtk_grid_new();
gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
/* 标签 + 输入框 */
gtk_grid_attach(GTK_GRID(grid), gtk_label_new("用户名:"), 0, 0, 1, 1);
gtk_grid_attach(GTK_GRID(grid), gtk_entry_new(), 1, 0, 2, 1);
gtk_grid_attach(GTK_GRID(grid), gtk_label_new("邮箱:"), 0, 1, 1, 1);
gtk_grid_attach(GTK_GRID(grid), gtk_entry_new(), 1, 1, 2, 1);
gtk_grid_attach(GTK_GRID(grid), gtk_label_new("年龄:"), 0, 2, 1, 1);
gtk_grid_attach(GTK_GRID(grid), gtk_spin_button_new_with_range(0, 150, 1), 1, 2, 1, 1);
/* 按钮跨越多列 */
GtkWidget *submit_btn = gtk_button_new_with_label("提交");
gtk_grid_attach(GTK_GRID(grid), submit_btn, 1, 3, 2, 1);
Python 布局示例
#!/usr/bin/env python3
"""GTK4 布局示例 - PyGObject"""
import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Gtk, Adw
class LayoutDemo(Adw.ApplicationWindow):
def __init__(self, app):
super().__init__(application=app)
self.set_title("GTK4 布局演示")
self.set_default_size(500, 400)
# 主垂直布局
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
main_box.set_margin_all(16)
self.set_content(main_box)
# HeaderBar 标题
header = Adw.HeaderBar()
header.set_title_widget(Gtk.Label(label="布局演示"))
# === Grid 表单 ===
group = Adw.PreferencesGroup(title="用户信息 / User Info")
main_box.append(group)
# 使用 Adw.ActionRow
name_row = Adw.EntryRow(title="姓名 / Name")
group.add(name_row)
email_row = Adw.EntryRow(title="邮箱 / Email")
group.add(email_row)
# === Box 按钮行 ===
btn_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
btn_box.set_halign(Gtk.Align.END)
main_box.append(btn_box)
cancel_btn = Gtk.Button(label="取消 / Cancel")
cancel_btn.add_css_class("flat")
btn_box.append(cancel_btn)
save_btn = Gtk.Button(label="保存 / Save")
save_btn.add_css_class("suggested-action")
btn_box.append(save_btn)
class LayoutApp(Adw.Application):
def __init__(self):
super().__init__(application_id="com.example.layout")
def do_activate(self):
LayoutDemo(self).present()
if __name__ == "__main__":
LayoutApp().run()
8.3 GtkListView 与模型 / GtkListView and Model
GTK4 的列表视图使用新的模型-视图架构。 GTK4’s list view uses a new Model-View architecture.
C 完整示例 / C Complete Example
/* listview-demo.c - GTK4 列表示例 */
#include <gtk/gtk.h>
/* 列表项工厂:创建控件 */
static GtkWidget *create_list_item(GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
GtkWidget *label_name = gtk_label_new(NULL);
GtkWidget *label_email = gtk_label_new(NULL);
gtk_widget_set_hexpand(label_name, TRUE);
gtk_widget_set_halign(label_name, GTK_ALIGN_START);
gtk_label_set_xalign(GTK_LABEL(label_name), 0.0);
gtk_box_append(GTK_BOX(box), label_name);
gtk_box_append(GTK_BOX(box), label_email);
gtk_list_item_set_child(list_item, box);
}
/* 列表项工厂:绑定数据 */
static void bind_list_item(GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *box = gtk_list_item_get_child(list_item);
GtkStringObject *obj = GTK_STRING_OBJECT(
gtk_list_item_get_item(list_item));
GtkWidget *label_name = gtk_widget_get_first_child(box);
GtkWidget *label_email = gtk_widget_get_last_child(box);
const char *text = gtk_string_object_get_string(obj);
gtk_label_set_text(GTK_LABEL(label_name), text);
}
static void on_activate(GtkApplication *app, gpointer user_data)
{
GtkWidget *window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(window), "GTK4 ListView 演示");
gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
/* 创建数据模型 */
GtkStringList *string_list = gtk_string_list_new(NULL);
gtk_string_list_append(string_list, "张三 - [email protected]");
gtk_string_list_append(string_list, "李四 - [email protected]");
gtk_string_list_append(string_list, "王五 - [email protected]");
gtk_string_list_append(string_list, "赵六 - [email protected]");
/* 创建工厂 */
GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
g_signal_connect(factory, "setup", G_CALLBACK(create_list_item), NULL);
g_signal_connect(factory, "bind", G_CALLBACK(bind_list_item), NULL);
/* 创建列表视图 */
GtkWidget *list_view = gtk_list_view_new(
GTK_SELECTION_MODEL(
gtk_single_selection_new(G_LIST_MODEL(string_list))),
factory);
/* 滚动容器 */
GtkWidget *scrolled = gtk_scrolled_window_new();
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled), list_view);
gtk_window_set_child(GTK_WINDOW(window), scrolled);
gtk_window_present(GTK_WINDOW(window));
}
int main(int argc, char *argv[])
{
GtkApplication *app = gtk_application_new(
"com.example.listview", 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;
}
Python 列表示例
"""GTK4 ListView 示例 - PyGObject"""
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk, GObject, Gio
class UserItem(GObject.Object):
__gtype_name__ = "UserItem"
name = GObject.Property(type=str)
email = GObject.Property(type=str)
def __init__(self, name: str, email: str):
super().__init__()
self.name = name
self.email = email
def create_list_item(factory, list_item):
"""创建列表项控件"""
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
box.set_margin_start(8)
box.set_margin_end(8)
box.set_margin_top(4)
box.set_margin_bottom(4)
name_label = Gtk.Label()
name_label.set_xalign(0.0)
name_label.set_hexpand(True)
box.append(name_label)
email_label = Gtk.Label()
email_label.add_css_class("dim-label")
box.append(email_label)
list_item.set_child(box)
def bind_list_item(factory, list_item):
"""绑定数据到控件"""
box = list_item.get_child()
user = list_item.get_item()
name_label = box.get_first_child()
email_label = box.get_last_child()
name_label.set_text(user.name)
email_label.set_text(user.email)
class ListApp(Gtk.Application):
def __init__(self):
super().__init__(application_id="com.example.listview")
self.connect("activate", self.on_activate)
def on_activate(self, app):
window = Gtk.ApplicationWindow(application=app)
window.set_title("GTK4 ListView 演示")
window.set_default_size(400, 300)
# 数据
users = Gio.ListStore.new(UserItem)
users.append(UserItem("张三", "[email protected]"))
users.append(UserItem("李四", "[email protected]"))
users.append(UserItem("王五", "[email protected]"))
# 工厂
factory = Gtk.SignalListItemFactory()
factory.connect("setup", create_list_item)
factory.connect("bind", bind_list_item)
# 列表视图
selection = Gtk.SingleSelection(model=users)
list_view = Gtk.ListView(model=selection, factory=factory)
# 滚动窗口
scrolled = Gtk.ScrolledWindow()
scrolled.set_child(list_view)
scrolled.set_vexpand(True)
window.set_content(scrolled)
window.present()
if __name__ == "__main__":
ListApp().run()
8.4 GtkColumnView / Column View (Tree)
/* GtkColumnView 类似树视图/表格视图 */
/* GtkColumnView is GTK4's replacement for GtkTreeView with columns */
static void setup_text_factory(GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label = gtk_label_new(NULL);
gtk_label_set_xalign(GTK_LABEL(label), 0.0);
gtk_list_item_set_child(list_item, label);
}
static void bind_name(GtkListItemFactory *factory, GtkListItem *list_item)
{
GtkWidget *label = gtk_list_item_get_child(list_item);
GtkStringObject *obj = GTK_STRING_OBJECT(
gtk_list_item_get_item(list_item));
gtk_label_set_text(GTK_LABEL(label),
gtk_string_object_get_string(obj));
}
/* 创建列视图 */
GtkWidget *create_column_view(GtkStringList *model)
{
GtkWidget *column_view = gtk_column_view_new(
GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(model))));
/* 列1: 名称 */
GtkListItemFactory *factory1 = gtk_signal_list_item_factory_new();
g_signal_connect(factory1, "setup", G_CALLBACK(setup_text_factory), NULL);
g_signal_connect(factory1, "bind", G_CALLBACK(bind_name), NULL);
GtkColumnViewColumn *col1 = gtk_column_view_column_new("名称", factory1);
gtk_column_view_append_column(GTK_COLUMN_VIEW(column_view), col1);
/* 列2: 值 */
GtkListItemFactory *factory2 = gtk_signal_list_item_factory_new();
g_signal_connect(factory2, "setup", G_CALLBACK(setup_text_factory), NULL);
g_signal_connect(factory2, "bind", G_CALLBACK(bind_name), NULL);
GtkColumnViewColumn *col2 = gtk_column_view_column_new("值", factory2);
gtk_column_view_append_column(GTK_COLUMN_VIEW(column_view), col2);
return column_view;
}
8.5 GtkGrid / Grid Layout
Python Grid 示例
"""GTK4 Grid 布局示例"""
def create_form_grid():
"""创建表单网格"""
grid = Gtk.Grid()
grid.set_column_spacing(12)
grid.set_row_spacing(8)
grid.set_margin_all(16)
# 行0:姓名
name_label = Gtk.Label(label="姓名:")
name_label.set_xalign(1.0)
name_entry = Gtk.Entry()
name_entry.set_hexpand(True)
grid.attach(name_label, 0, 0, 1, 1)
grid.attach(name_entry, 1, 0, 2, 1)
# 行1:邮箱
email_label = Gtk.Label(label="邮箱:")
email_label.set_xalign(1.0)
email_entry = Gtk.Entry()
email_entry.set_hexpand(True)
grid.attach(email_label, 0, 1, 1, 1)
grid.attach(email_entry, 1, 1, 2, 1)
# 行2:年龄
age_label = Gtk.Label(label="年龄:")
age_label.set_xalign(1.0)
age_spin = Gtk.SpinButton.new_with_range(0, 150, 1)
grid.attach(age_label, 0, 2, 1, 1)
grid.attach(age_spin, 1, 2, 1, 1)
# 行3:按钮
btn_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
btn_box.set_halign(Gtk.Align.END)
cancel_btn = Gtk.Button(label="取消")
save_btn = Gtk.Button(label="保存")
save_btn.add_css_class("suggested-action")
btn_box.append(cancel_btn)
btn_box.append(save_btn)
grid.attach(btn_box, 1, 3, 2, 1)
return grid
8.6 弹窗与对话框 / Popups and Dialogs
GtkAlertDialog (GTK 4.10+)
/* GTK4 现代对话框 */
static void on_save_response(GObject *source, GAsyncResult *result,
gpointer user_data)
{
GtkAlertDialog *dialog = GTK_ALERT_DIALOG(source);
GError *error = NULL;
int response = gtk_alert_dialog_choose_finish(dialog, result, &error);
if (error) {
g_printerr("Error: %s\n", error->message);
g_error_free(error);
return;
}
if (response == 0) {
g_print("User clicked Save\n");
} else {
g_print("User clicked Discard\n");
}
}
static void show_save_dialog(GtkWindow *parent)
{
GtkAlertDialog *dialog = gtk_alert_dialog_new(
"文档已修改。是否保存?");
const char *buttons[] = {"保存", "丢弃", "取消", NULL};
gtk_alert_dialog_set_buttons(dialog, buttons);
gtk_alert_dialog_set_cancel_button(dialog, 2); // 取消 = ESC
gtk_alert_dialog_set_default_button(dialog, 0); // 保存 = 默认
gtk_alert_dialog_choose(dialog, parent, NULL,
on_save_response, NULL);
}
Python 弹窗示例
"""GTK4 弹窗示例"""
def show_info_dialog(parent):
"""信息弹窗"""
dialog = Adw.MessageDialog.new(parent, "提示", "操作已成功完成")
dialog.add_response("ok", "确定")
dialog.set_default_response("ok")
dialog.set_response_appearance("ok", Adw.ResponseAppearance.SUGGESTED)
dialog.present()
def show_confirm_dialog(parent, on_response):
"""确认弹窗"""
dialog = Adw.MessageDialog.new(parent, "确认", "确定要删除此项吗?")
dialog.add_response("cancel", "取消")
dialog.add_response("delete", "删除")
dialog.set_default_response("cancel")
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
dialog.connect("response", on_response)
dialog.present()
def show_toast(window, message):
"""Toast 通知"""
toast = Adw.Toast.new(message)
toast.set_timeout(3)
window.get_toast_overlay().add_toast(toast)
8.7 CSS 样式 / CSS Styling
GTK4 CSS 基础 / CSS Basics
/* custom.css - GTK4 CSS 样式 */
/* 基本控件样式 */
button {
border-radius: 8px;
padding: 8px 16px;
font-weight: bold;
}
/* 特定类名 */
button.suggested-action {
background-color: @accent_bg_color;
color: @accent_fg_color;
}
button.destructive-action {
background-color: @error_bg_color;
color: @error_fg_color;
}
/* 自定义类 */
button.my-button {
background-color: #3498db;
color: white;
border-radius: 12px;
padding: 12px 24px;
font-size: 14px;
}
button.my-button:hover {
background-color: #2980b9;
}
button.my-button:active {
background-color: #21618c;
}
/* 标签样式 */
.title-label {
font-size: 24px;
font-weight: bold;
color: @accent_bg_color;
margin-bottom: 12px;
}
.subtitle-label {
font-size: 14px;
color: @insensitive_fg_color;
}
/* 输入框 */
entry {
border-radius: 6px;
padding: 8px;
min-height: 20px;
}
entry:focus {
border-color: @accent_bg_color;
}
/* 卡片样式 */
.card {
background-color: @card_bg_color;
border-radius: 12px;
padding: 16px;
border: 1px solid @borders;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
/* 侧边栏 */
.sidebar {
background-color: @headerbar_bg_color;
border-right: 1px solid @borders;
}
/* 滚动条 */
scrollbar slider {
min-width: 6px;
min-height: 6px;
border-radius: 3px;
}
加载 CSS / Loading CSS
/* C: 加载 CSS 文件 */
static void load_css(void)
{
GtkCssProvider *provider = gtk_css_provider_new();
gtk_css_provider_load_from_path(provider, "custom.css");
gtk_style_context_add_provider_for_display(
gdk_display_get_default(),
GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref(provider);
}
"""Python: 加载 CSS 文件"""
def load_css():
provider = Gtk.CssProvider()
provider.load_from_path("custom.css")
Gtk.StyleContext.add_provider_for_display(
Gdk.Display.get_default(),
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
GTK4 CSS 变量 / CSS Variables
| 变量 / Variable | 用途 / Purpose |
|---|---|
@accent_bg_color | 主题强调色背景 / Accent background |
@accent_fg_color | 强调色前景 / Accent foreground |
@window_bg_color | 窗口背景 / Window background |
@card_bg_color | 卡片背景 / Card background |
@headerbar_bg_color | 头部栏背景 / Header bar background |
@error_bg_color | 错误色背景 / Error background |
@success_bg_color | 成功色背景 / Success background |
@warning_bg_color | 警告色背景 / Warning background |
@borders | 边框色 / Border color |
@insensitive_fg_color | 禁用前景色 / Disabled foreground |
注意事项 / Important Notes
⚠️ GTK4 无 GtkContainer / No GtkContainer in GTK4
GTK4 移除了
GtkContainer。使用gtk_box_append()、gtk_grid_attach()等直接添加子控件。 GTK4 removedGtkContainer. Usegtk_box_append(),gtk_grid_attach()etc.
⚠️ CSS 差异 / CSS Differences
GTK CSS 不支持所有 Web CSS 特性。不支持 flexbox、grid 布局。 颜色使用 GTK 自定义变量(如
@accent_bg_color)。GTK CSS doesn’t support all web CSS features. No flexbox/grid.
⚠️ GtkTreeView 仍在 / GtkTreeView Still Exists
GTK4 仍保留
GtkTreeView,但推荐使用GtkListView+GListModel新架构。GtkTreeViewexists in GTK4 butGtkListView+GListModelis recommended.
扩展阅读 / Further Reading
| 资源 / Resource | 链接 / Link |
|---|---|
| GTK4 控件列表 | https://docs.gtk.org/gtk4/visual_index.html |
| GTK4 CSS 文档 | https://docs.gtk.org/gtk4/css-properties.html |
| GtkListView 教程 | https://docs.gtk.org/gtk4/list-widget.html |
| GTK4 示例 | https://gitlab.gnome.org/GNOME/gtk/-/tree/main/examples |