Vala 语言入门教程 / 08 - GTK 应用开发
第 8 章:GTK 应用开发
GTK 是 GNOME 生态的核心 GUI 框架。Vala 与 GTK 的结合堪称完美——简洁的语法加上原生的 GObject 支持。
8.1 GTK 4 架构概览
┌─────────────────────────────────────┐
│ libadwaita (自适应 UI) │
├─────────────────────────────────────┤
│ GTK 4 (GUI 框架) │
├─────────────────────────────────────┤
│ GIO / Pango / Cairo / GdkPixbuf │
├─────────────────────────────────────┤
│ GLib / GObject │
└─────────────────────────────────────┘
GTK 4 vs GTK 3 的主要区别:
| 特性 | GTK 3 | GTK 4 |
|---|---|---|
| 渲染 | GDK + Cairo | 渲染节点树 + GPU |
| 事件处理 | GdkEvent | GtkEventController |
| 布局 | GtkBox + GtkGrid | GtkBox + GtkGrid + GtkCenterBox |
| 动画 | GtkRevealer | GtkPropertyAnimation |
| 样式 | CSS 2.1 | CSS 2.1+ |
| 平滑滚动 | 有限 | 原生支持 |
| 自适应布局 | 需要 libhandy | libadwaita |
8.2 第一个 GTK 应用
8.2.1 最小的 GTK 4 应用
// hello_gtk.vala
using Gtk;
// 应用程序类
public class MyApp : Adw.Application {
public MyApp () {
Object (
application_id: "com.example.HelloApp",
flags: ApplicationFlags.FLAGS_NONE
);
}
protected override void activate () {
// 创建窗口
var window = new Adw.ApplicationWindow (this) {
title = "Hello Vala + GTK",
default_width = 400,
default_height = 300
};
// 创建标签
var label = new Label ("你好,Vala + GTK 4!");
// 设置窗口内容
window.set_content (label);
// 显示窗口
window.present ();
}
}
void main (string[] args) {
var app = new MyApp ();
app.run (args);
}
编译命令:
valac \
--pkg gtk4 \
--pkg libadwaita-1 \
-o hello_gtk \
hello_gtk.vala
Meson 构建:
project('hello_gtk', 'vala', 'c',
version: '1.0.0',
meson_version: '>= 0.62.0'
)
gtk4_dep = dependency('gtk4')
libadwaita_dep = dependency('libadwaita-1')
executable('hello_gtk',
'src/hello_gtk.vala',
dependencies: [gtk4_dep, libadwaita_dep],
install: true
)
8.3 窗口和应用结构
8.3.1 Adw.Application 结构
using Gtk;
using Adw;
public class MyApplication : Application {
private ApplicationWindow window;
public MyApplication () {
Object (
application_id: "com.example.MyApp",
flags: ApplicationFlags.FLAGS_NONE
);
}
protected override void activate () {
// 使用 Adw.Application 的样式管理
var style_manager = StyleManager.get_default ();
style_manager.color_scheme = ColorScheme.PREFER_LIGHT;
// 创建主窗口
window = new ApplicationWindow (this) {
title = "我的应用",
default_width = 800,
default_height = 600
};
// 构建 UI
build_ui ();
// 显示窗口
window.present ();
}
private void build_ui () {
// 使用 HeaderBar
var header = new HeaderBar ();
// 创建工具栏按钮
var menu_button = new MenuButton ();
menu_button.icon_name = "open-menu-symbolic";
menu_button.tooltip_text = "菜单";
header.pack_end (menu_button);
// 创建主内容区域
var content = new Box (Orientation.VERTICAL, 0);
content.append (header);
var label = new Label ("欢迎使用!");
label.vexpand = true;
content.append (label);
// 设置窗口内容
var toolbar_view = new ToolbarView ();
toolbar_view.add_top_bar (header);
toolbar_view.set_content (label);
window.set_content (toolbar_view);
}
}
void main (string[] args) {
var app = new MyApplication ();
app.run (args);
}
8.3.2 多窗口管理
using Gtk;
using Adw;
public class MultiWindowApp : Application {
private GLib.List<ApplicationWindow> windows =
new GLib.List<ApplicationWindow> ();
public MultiWindowApp () {
Object (
application_id: "com.example.MultiWindow",
flags: ApplicationFlags.FLAGS_NONE
);
}
protected override void activate () {
create_window ();
}
private ApplicationWindow create_window () {
var window = new ApplicationWindow (this) {
title = "窗口 %u".printf (windows.length () + 1),
default_width = 400,
default_height = 300
};
// 新建窗口按钮
var button = new Button.with_label ("新建窗口");
button.clicked.connect (() => {
create_window ();
});
var box = new Box (Orientation.VERTICAL, 12);
box.valign = Align.CENTER;
box.halign = Align.CENTER;
box.append (button);
var toolbar_view = new ToolbarView ();
var header = new HeaderBar ();
toolbar_view.add_top_bar (header);
toolbar_view.set_content (box);
window.set_content (toolbar_view);
window.present ();
windows.append (window);
window.destroy.connect (() => {
windows.remove (window);
if (windows.length () == 0) {
quit ();
}
});
return window;
}
}
void main (string[] args) {
var app = new MultiWindowApp ();
app.run (args);
}
8.4 常用控件
8.4.1 文本和输入
using Gtk;
using Adw;
public class InputDemo : Adw.Application {
public InputDemo () {
Object (application_id: "com.example.InputDemo",
flags: ApplicationFlags.FLAGS_NONE);
}
protected override void activate () {
var window = new Adw.ApplicationWindow (this) {
title = "输入控件演示",
default_width = 500,
default_height = 400
};
var box = new Box (Orientation.VERTICAL, 12) {
margin_top = 24,
margin_bottom = 24,
margin_start = 24,
margin_end = 24
};
// 标签
var title = new Label ("用户信息");
title.add_css_class ("title-1");
box.append (title);
// 文本输入
var name_entry = new Entry () {
placeholder_text = "请输入姓名"
};
box.append (create_row ("姓名", name_entry));
// 密码输入
var password_entry = new PasswordEntry () {
placeholder_text = "请输入密码"
};
box.append (create_row ("密码", password_entry));
// 数字输入
var age_spin = new SpinButton.with_range (0, 150, 1) {
value = 25
};
box.append (create_row ("年龄", age_spin));
// 搜索框
var search = new SearchEntry () {
placeholder_text = "搜索..."
};
search.search_changed.connect (() => {
print ("搜索: %s\n", search.text);
});
box.append (create_row ("搜索", search));
// 多行文本
var text_view = new TextView ();
text_view.set_size_request (-1, 100);
var scrolled = new ScrolledWindow () {
child = text_view,
vexpand = true
};
box.append (scrolled);
// 按钮
var button = new Button.with_label ("提交");
button.add_css_class ("suggested-action");
button.clicked.connect (() => {
var buffer = text_view.buffer;
string text;
buffer.get_text (out text, null, false);
print ("姓名: %s, 年龄: %.0f\n", name_entry.text, age_spin.value);
print ("备注: %s\n", text);
});
box.append (button);
window.set_content (box);
window.present ();
}
private Adw.ActionRow create_row (string title, Widget widget) {
var row = new Adw.ActionRow ();
row.title = title;
row.add_suffix (widget);
return row;
}
}
void main (string[] args) {
new InputDemo ().run (args);
}
8.4.2 按钮和选择
using Gtk;
using Adw;
public class ButtonDemo : Adw.Application {
public ButtonDemo () {
Object (application_id: "com.example.ButtonDemo",
flags: ApplicationFlags.FLAGS_NONE);
}
protected override void activate () {
var window = new Adw.ApplicationWindow (this) {
title = "按钮和选择",
default_width = 500,
default_height = 600
};
var box = new Box (Orientation.VERTICAL, 12) {
margin_top = 24,
margin_bottom = 24,
margin_start = 24,
margin_end = 24
};
// 普通按钮
var normal_btn = new Button.with_label ("普通按钮");
normal_btn.clicked.connect (() => {
print ("普通按钮被点击\n");
});
box.append (normal_btn);
// 图标按钮
var icon_btn = new Button.from_icon_name ("document-new") {
tooltip_text = "新建"
};
box.append (icon_btn);
// 开关
var toggle = new Switch () {
active = true,
halign = Align.START
};
toggle.state_set.connect ((state) => {
print ("开关: %s\n", state ? "开" : "关");
return false;
});
box.append (create_row ("通知", toggle));
// 复选框
var check = new CheckButton.with_label ("同意条款");
check.toggled.connect (() => {
print ("复选框: %s\n", check.active ? "选中" : "未选中");
});
box.append (check);
// 单选按钮组
var radio1 = new CheckButton.with_label ("选项 A");
var radio2 = new CheckButton.with_label ("选项 B");
radio2.set_group (radio1);
var radio3 = new CheckButton.with_label ("选项 C");
radio3.set_group (radio1);
radio1.active = true;
var radio_box = new Box (Orientation.VERTICAL, 6);
radio_box.append (radio1);
radio_box.append (radio2);
radio_box.append (radio3);
box.append (radio_box);
// 下拉菜单
var dropdown = new DropDown.from_strings (
{"选项一", "选项二", "选项三", "选项四"}
);
dropdown.notify["selected"].connect (() => {
print ("选择: %u\n", dropdown.selected);
});
box.append (create_row ("选择", dropdown));
// 滑块
var scale = new Scale.with_range (Orientation.HORIZONTAL, 0, 100, 1) {
value = 50,
hexpand = true
};
scale.value_changed.connect (() => {
print ("滑块值: %.0f\n", scale.get_value ());
});
box.append (create_row ("音量", scale));
// 进度条
var progress = new ProgressBar () {
fraction = 0.65,
show_text = true,
text = "65%"
};
box.append (progress);
window.set_content (box);
window.present ();
}
private Adw.ActionRow create_row (string title, Widget widget) {
var row = new Adw.ActionRow ();
row.title = title;
row.add_suffix (widget);
return row;
}
}
void main (string[] args) {
new ButtonDemo ().run (args);
}
8.4.3 列表和树视图
using Gtk;
using Adw;
public class ListDemo : Adw.Application {
public ListDemo () {
Object (application_id: "com.example.ListDemo",
flags: ApplicationFlags.FLAGS_NONE);
}
protected override void activate () {
var window = new Adw.ApplicationWindow (this) {
title = "列表演示",
default_width = 400,
default_height = 500
};
// 使用 ListBox(GTK 4 推荐)
var listbox = new ListBox ();
listbox.selection_mode = SelectionMode.SINGLE;
// 添加列表项
string[] items = {
"文件管理器", "终端", "文本编辑器",
"浏览器", "音乐播放器", "视频播放器"
};
foreach (var item in items) {
var row = new Adw.ActionRow ();
row.title = item;
row.subtitle = "应用程序";
row.add_suffix (new Image.from_icon_name ("go-next-symbolic"));
listbox.append (row);
}
// 选择事件
listbox.row_selected.connect ((row) => {
if (row != null) {
var action_row = (Adw.ActionRow) row;
print ("选中: %s\n", action_row.title);
}
});
// 使用 ColumnView(更高级的列表)
var model = new GLib.ListStore (typeof (string));
model.append ("项目 1");
model.append ("项目 2");
model.append ("项目 3");
var scrolled = new ScrolledWindow () {
child = listbox,
vexpand = true
};
window.set_content (scrolled);
window.present ();
}
}
void main (string[] args) {
new ListDemo ().run (args);
}
8.5 布局系统
8.5.1 Box 布局
using Gtk;
using Adw;
public class LayoutDemo : Adw.Application {
public LayoutDemo () {
Object (application_id: "com.example.LayoutDemo",
flags: ApplicationFlags.FLAGS_NONE);
}
protected override void activate () {
var window = new Adw.ApplicationWindow (this) {
title = "布局演示",
default_width = 600,
default_height = 400
};
// 垂直布局
var vbox = new Box (Orientation.VERTICAL, 12) {
margin_top = 24,
margin_bottom = 24,
margin_start = 24,
margin_end = 24
};
// 水平布局
var hbox = new Box (Orientation.HORIZONTAL, 12);
var btn1 = new Button.with_label ("按钮 1");
btn1.hexpand = true;
var btn2 = new Button.with_label ("按钮 2");
btn2.hexpand = true;
var btn3 = new Button.with_label ("按钮 3");
btn3.hexpand = true;
hbox.append (btn1);
hbox.append (btn2);
hbox.append (btn3);
vbox.append (hbox);
// Grid 布局
var grid = new Grid () {
row_spacing = 12,
column_spacing = 12,
hexpand = true
};
grid.attach (new Label ("用户名:"), 0, 0);
grid.attach (new Entry () { hexpand = true }, 1, 0);
grid.attach (new Label ("密码:"), 0, 1);
grid.attach (new PasswordEntry () { hexpand = true }, 1, 1);
var login_btn = new Button.with_label ("登录");
login_btn.add_css_class ("suggested-action");
grid.attach (login_btn, 0, 2, 2, 1);
vbox.append (grid);
// CenterBox(三栏布局)
var center = new CenterBox ();
center.set_start_widget (new Label ("左"));
center.set_center_widget (new Label ("中"));
center.set_end_widget (new Label ("右"));
vbox.append (center);
// 带分割线的布局
var paned = new Paned (Orientation.HORIZONTAL) {
start_child = new Label ("左侧内容"),
end_child = new Label ("右侧内容"),
position = 200,
vexpand = true
};
vbox.append (paned);
window.set_content (vbox);
window.present ();
}
}
void main (string[] args) {
new LayoutDemo ().run (args);
}
8.5.2 响应式布局(libadwaita)
using Gtk;
using Adw;
public class AdaptiveDemo : Adw.Application {
public AdaptiveDemo () {
Object (application_id: "com.example.AdaptiveDemo",
flags: ApplicationFlags.FLAGS_NONE);
}
protected override void activate () {
var window = new Adw.ApplicationWindow (this) {
title = "自适应布局",
default_width = 400,
default_height = 600
};
// 使用 Adw.NavigationSplitView(自适应导航)
var sidebar = new Adw.NavigationPage (
create_sidebar (),
"侧边栏"
);
var content = new Adw.NavigationPage (
create_content (),
"内容"
);
var split_view = new Adw.NavigationSplitView () {
sidebar = sidebar,
content = content,
min_sidebar_width = 200,
max_sidebar_width = 300
};
window.set_content (split_view);
window.present ();
}
private Widget create_sidebar () {
var listbox = new ListBox ();
for (int i = 1; i <= 10; i++) {
var row = new Adw.ActionRow ();
row.title = "项目 %d".printf (i);
listbox.append (row);
}
var scrolled = new ScrolledWindow () {
child = listbox,
vexpand = true
};
var box = new Box (Orientation.VERTICAL, 0);
var header = new Adw.HeaderBar ();
box.append (header);
box.append (scrolled);
return box;
}
private Widget create_content () {
var label = new Label ("选择左侧的项目") {
vexpand = true
};
var box = new Box (Orientation.VERTICAL, 0);
var header = new Adw.HeaderBar ();
box.append (header);
box.append (label);
return box;
}
}
void main (string[] args) {
new AdaptiveDemo ().run (args);
}
8.6 对话框
8.6.1 消息对话框
using Gtk;
using Adw;
public class DialogDemo : Adw.Application {
private Adw.ApplicationWindow window;
public DialogDemo () {
Object (application_id: "com.example.DialogDemo",
flags: ApplicationFlags.FLAGS_NONE);
}
protected override void activate () {
window = new Adw.ApplicationWindow (this) {
title = "对话框演示",
default_width = 400,
default_height = 300
};
var box = new Box (Orientation.VERTICAL, 12) {
margin_top = 24,
margin_bottom = 24,
margin_start = 24,
margin_end = 24,
valign = Align.CENTER,
halign = Align.CENTER
};
// 消息对话框
var info_btn = new Button.with_label ("信息对话框");
info_btn.clicked.connect (show_info_dialog);
box.append (info_btn);
// 确认对话框
var confirm_btn = new Button.with_label ("确认对话框");
confirm_btn.clicked.connect (show_confirm_dialog);
box.append (confirm_btn);
// 输入对话框
var input_btn = new Button.with_label ("输入对话框");
input_btn.clicked.connect (show_input_dialog);
box.append (input_btn);
// 文件选择器
var file_btn = new Button.with_label ("文件选择器");
file_btn.clicked.connect (show_file_dialog);
box.append (file_btn);
window.set_content (box);
window.present ();
}
private void show_info_dialog () {
var dialog = new Adw.AlertDialog ("提示", "操作已完成!");
dialog.add_response ("ok", "确定");
dialog.default_response = "ok";
dialog.present (window);
}
private void show_confirm_dialog () {
var dialog = new Adw.AlertDialog ("确认", "确定要删除吗?");
dialog.add_response ("cancel", "取消");
dialog.add_response ("delete", "删除");
dialog.set_response_appearance ("delete", Adw.ResponseAppearance.DESTRUCTIVE);
dialog.default_response = "cancel";
dialog.close_response = "cancel";
dialog.response.connect ((response) => {
if (response == "delete") {
print ("已删除\n");
}
});
dialog.present (window);
}
private void show_input_dialog () {
var entry = new Entry () {
placeholder_text = "请输入内容"
};
var dialog = new Adw.AlertDialog ("输入", null);
dialog.set_extra_child (entry);
dialog.add_response ("cancel", "取消");
dialog.add_response ("ok", "确定");
dialog.default_response = "ok";
dialog.response.connect ((response) => {
if (response == "ok") {
print ("输入内容: %s\n", entry.text);
}
});
dialog.present (window);
}
private async void show_file_dialog () {
var dialog = new FileDialog ();
dialog.title = "选择文件";
try {
var file = yield dialog.open (window, null);
if (file != null) {
print ("选择的文件: %s\n", file.get_path ());
}
} catch (GLib.Error e) {
printerr ("错误: %s\n", e.message);
}
}
}
void main (string[] args) {
new DialogDemo ().run (args);
}
8.7 GResource
8.7.1 资源文件
GResource 将文件(图片、UI、CSS 等)嵌入到二进制文件中。
目录结构:
data/
├── resources.gresource.xml
├── ui/
│ └── window.ui
├── icons/
│ └── hicolor/
│ └── scalable/
│ └── apps/
│ └── com.example.MyApp.svg
└── style.css
resources.gresource.xml:
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/example/MyApp">
<file preprocess="xml-stripblanks">ui/window.ui</file>
<file>style.css</file>
<file preprocess="xml-stripblanks">icons/hicolor/scalable/apps/com.example.MyApp.svg</file>
</gresource>
</gresources>
meson.build(资源编译):
gnome = import('gnome')
resources = gnome.compile_resources(
'resources',
'data/resources.gresource.xml',
source_dir: 'data',
c_name: 'myapp'
)
executable('myapp',
'src/main.vala',
resources,
dependencies: [gtk4_dep, libadwaita_dep],
install: true
)
8.7.2 在代码中使用资源
// 加载 CSS
var css_provider = new CssProvider ();
css_provider.load_from_resource ("/com/example/MyApp/style.css");
StyleContext.add_provider_for_display (
Gdk.Display.get_default (),
css_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
// 加载图标
var icon = new GLib.ThemedIcon ("com.example.MyApp");
// 加载 UI 文件
var builder = new Gtk.Builder ();
builder.add_from_resource ("/com/example/MyApp/ui/window.ui", null);
8.8 UI 文件(Builder)
8.8.1 使用 Gtk.Builder 加载 UI
window.ui:
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0"/>
<object class="AdwApplicationWindow" id="main_window">
<property name="title">My App</property>
<property name="default-width">800</property>
<property name="default-height">600</property>
<child>
<object class="GtkBox" id="main_box">
<property name="orientation">vertical</property>
<child>
<object class="AdwHeaderBar" id="header"/>
</child>
<child>
<object class="GtkLabel" id="welcome_label">
<property name="label">欢迎!</property>
<property name="vexpand">true</property>
<property name="css-classes">title-1</property>
</object>
</child>
<child>
<object class="GtkButton" id="action_button">
<property name="label">点击我</property>
<property name="css-classes">suggested-action</property>
<signal name="clicked" handler="on_action_clicked"/>
</object>
</child>
</object>
</child>
</object>
</interface>
main.vala:
using Gtk;
using Adw;
[GtkTemplate (ui = "/com/example/MyApp/ui/window.ui")]
public class MyWindow : Adw.ApplicationWindow {
[GtkChild]
private unowned Label welcome_label;
[GtkChild]
private unowned Button action_button;
public MyWindow (Adw.Application app) {
Object (application: app);
}
[GtkCallback]
private void on_action_clicked () {
welcome_label.label = "按钮已被点击!";
}
}
public class MyApp : Adw.Application {
public MyApp () {
Object (application_id: "com.example.MyApp",
flags: ApplicationFlags.FLAGS_NONE);
}
protected override void activate () {
var window = new MyWindow (this);
window.present ();
}
}
void main (string[] args) {
new MyApp ().run (args);
}
8.9 业务场景:联系人管理应用
using Gtk;
using Adw;
// 联系人数据模型
public class Contact : Object {
public string name { get; set; }
public string phone { get; set; }
public string email { get; set; }
public Contact (string name, string phone, string email) {
Object (name: name, phone: phone, email: email);
}
}
// 主应用
public class ContactApp : Adw.Application {
private GLib.ListStore contacts;
private ListBox listbox;
public ContactApp () {
Object (application_id: "com.example.ContactApp",
flags: ApplicationFlags.FLAGS_NONE);
}
protected override void activate () {
contacts = new GLib.ListStore (typeof (Contact));
// 添加示例数据
contacts.append (new Contact ("张三", "138-0000-0001", "zhang@example.com"));
contacts.append (new Contact ("李四", "139-0000-0002", "li@example.com"));
contacts.append (new Contact ("王五", "137-0000-0003", "wang@example.com"));
var window = new Adw.ApplicationWindow (this) {
title = "联系人",
default_width = 400,
default_height = 600
};
// 构建界面
var toolbar_view = new ToolbarView ();
var header = new Adw.HeaderBar ();
var add_button = new Button.from_icon_name ("list-add-symbolic");
add_button.tooltip_text = "添加联系人";
add_button.clicked.connect (() => {
show_add_dialog (window);
});
header.pack_end (add_button);
toolbar_view.add_top_bar (header);
// 列表
listbox = new ListBox ();
listbox.selection_mode = SelectionMode.SINGLE;
listbox.add_css_class ("boxed-list");
// 绑定数据
bind_list ();
var scrolled = new ScrolledWindow () {
child = listbox,
vexpand = true,
hexpand = true
};
toolbar_view.set_content (scrolled);
window.set_content (toolbar_view);
window.present ();
}
private void bind_list () {
// 手动绑定列表
for (uint i = 0; i < contacts.get_n_items (); i++) {
var contact = (Contact) contacts.get_item (i);
listbox.append (create_contact_row (contact));
}
}
private Widget create_contact_row (Contact contact) {
var row = new Adw.ActionRow ();
row.title = contact.name;
row.subtitle = "%s | %s".printf (contact.phone, contact.email);
row.add_prefix (new Image.from_icon_name ("avatar-default-symbolic"));
var delete_btn = new Button.from_icon_name ("user-trash-symbolic") {
css_classes = {"flat", "circular"},
tooltip_text = "删除"
};
delete_btn.clicked.connect (() => {
print ("删除: %s\n", contact.name);
});
row.add_suffix (delete_btn);
return row;
}
private void show_add_dialog (Adw.ApplicationWindow parent) {
var name_entry = new Entry () { placeholder_text = "姓名" };
var phone_entry = new Entry () { placeholder_text = "电话" };
var email_entry = new Entry () { placeholder_text = "邮箱" };
var box = new Box (Orientation.VERTICAL, 12);
box.append (name_entry);
box.append (phone_entry);
box.append (email_entry);
var dialog = new Adw.AlertDialog ("添加联系人", null);
dialog.set_extra_child (box);
dialog.add_response ("cancel", "取消");
dialog.add_response ("add", "添加");
dialog.default_response = "add";
dialog.response.connect ((response) => {
if (response == "add") {
var contact = new Contact (
name_entry.text,
phone_entry.text,
email_entry.text
);
contacts.append (contact);
listbox.append (create_contact_row (contact));
}
});
dialog.present (parent);
}
}
void main (string[] args) {
new ContactApp ().run (args);
}
8.10 注意事项
⚠️ GTK 开发常见陷阱
- 使用 GTK 4 而非 GTK 3:新项目应使用 GTK 4
- libadwaita 是推荐的:它提供了 GNOME 风格的自适应控件
- UI 线程:GTK 不是线程安全的,所有 UI 操作必须在主线程
- 长耗时操作:使用
async避免阻塞 UI - 资源编译:使用 Meson 的
gnome.compile_resources()编译资源 - CSS 样式:GTK 4 使用 CSS 进行样式化
8.11 扩展阅读
| 资源 | 链接 |
|---|---|
| GTK 4 文档 | https://docs.gtk.org/gtk4/ |
| libadwaita 文档 | https://gnome.pages.gitlab.gnome.org/libadwaita/ |
| GTK 4 Widget 工厂 | https://apps.gnome.org/Workbench/ |
| GNOME 开发者指南 | https://developer.gnome.org/ |
| GTK 4 示例 | https://gitlab.gnome.org/GNOME/gtk/-/tree/main/examples |
8.12 总结
| 要点 | 说明 |
|---|---|
| 框架 | GTK 4 + libadwaita |
| 应用类 | Adw.Application |
| 窗口类 | Adw.ApplicationWindow |
| 布局 | Box、Grid、CenterBox、Paned |
| 对话框 | Adw.AlertDialog、FileDialog |
| 资源 | GResource + gnome.compile_resources() |
| UI 文件 | Gtk.Builder + .ui XML |
下一章我们将学习 POSIX 绑定和系统编程。→ 第 9 章:POSIX 绑定