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

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

变化 / ChangeGTK3GTK4
控件可见性gtk_widget_show_all()默认可见 / Visible by default
渲染引擎GDK + CairoGSK (GPU 加速)
事件处理GtkEventBox直接处理 / Direct on widgets
布局管理GtkContainerGtkLayoutManager
CSS 节点有限支持完整 CSS 节点树
列表控件GtkTreeViewGtkListView + GListModel
拖放GtkDragSourceGtkDragSource / 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 现代化