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

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, "张三 - zhangsan@example.com");
    gtk_string_list_append(string_list, "李四 - lisi@example.com");
    gtk_string_list_append(string_list, "王五 - wangwu@example.com");
    gtk_string_list_append(string_list, "赵六 - zhaoliu@example.com");

    /* 创建工厂 */
    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("张三", "zhangsan@example.com"))
        users.append(UserItem("李四", "lisi@example.com"))
        users.append(UserItem("王五", "wangwu@example.com"))

        # 工厂
        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 removed GtkContainer. Use gtk_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 新架构。 GtkTreeView exists in GTK4 but GtkListView + GListModel is 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

07 - GTK 基础 | 09 - libadwaita 现代化