GUI development with Rust and GTK4 阅读笔记
简记
这是我第二次从头开始阅读,有第一次的印象要容易不少。
如果只关心具体的做法,而不思考为什么这样做,以及整体的框架,阅读的过程将会举步维艰。
简略记录 gtk-rs 的书中提到的点。对同一个问题书中所演示了多种处理方法,而且跨度比较大,第一次阅读的时候经常出现忘记之前的内容。
fn signals() -> &'static [Signal] {
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
SIGNALS.get_or_init(|| {
vec![Signal::builder("max-number-reached")
.param_types([i32::static_type()])
.build()]
})
}
signals:
max-number-reached(i32)
GTK GObject 的缺点
- Reference cycles
- Not thread safe
使用异步块/函数一般是通过 glib::spawn_future_local() spawn.
异步函数之间的通信是使用 async_channel。
有些异步库依赖 tokio runtime 不能直接通过 GLib 的主线程 spawn。 通过这样一个函数返回的 runtime 来 spawn。
use std::sync::OnceLock;
use tokio::runtime::Runtime;
fn runtime() -> &'static Runtime {
static RUNTIME: OnceLock<Runtime> = OnceLock::new();
RUNTIME.get_or_init(|| {
Runtime::new().expect("Setting up tokio runtime needs to succeed.")
})
}
设置(Settings)
GTK 应用的设置是通过一个 .gschema.xml 文件描述。
<?xml version="1.0" encoding="utf-8"?>
<schemalist>
<schema id="org.gtk_rs.Settings1" path="/org/gtk_rs/Settings1/">
<key name="is-switch-enabled" type="b">
<default>false</default>
<summary>Default switch state</summary>
</key>
</schema>
</schemalist>
设置是在应用关闭后再打开,仍然保持关闭前的状态。这个 GSchema xml 需要编译安装。
mkdir -p $HOME/.local/share/glib-2.0/schemas
cp org.gtk_rs.Settings1.gschema.xml $HOME/.local/share/glib-2.0/schemas/
glib-compile-schemas $HOME/.local/share/glib-2.0/schemas/
settings 的状态绑定到 widget,是调用 settings 的 bind() 方法。把某个属性绑定到某一个属性是通过源对象 bind_property() 方法
第8章 Saving Window State 还是使用 GTK 的设置,只不过在 GTK 的对象系统中使用了自己定义的方法(在 Rust 中我分不出哪个是class struct 和 instance struct,不过我知道状态放在 imp.rs,方法一般是 mod.rs ,如果实际写的时候放在一个文件里面就是把 imp.rs 中的内容放到对应的 mod.rs 并且用 mod imp {} 包裹)。
List Widgets
当 ListView 创建 ListItem 的时候会调 factory.connect_setup 注册的回调,
factory.connect_bind 是将 Model 中的数据绑定到单独的 List Item,
绑定数据到 Widget:
- 设置 Widget 的效果
label.set_label(&integer_object.number().to_string()); - 将数据的属性绑定到 Widget 的属性
integer_object
.bind_property("number", &label, "label")
.sync_create()
.build();
- 在
connect_setup里面完成绑定,可以解决上一个解决方案的问题
list_item
.property_expression("item")
.chain_property::<IntegerObject>("number")
.bind(&label, "label", Widget::NONE);
// 拿到 list_item 中的 item (data) 中的 number 属性,绑定到实际的 Widget
Composite Templates
除了直接编写 Rust 代码创造界面,还可以使用 XML 文件描述窗口组件。在 XML 文件里面可以使用直接在 Rust 代码中继承的组件。
如果要手动设置组件中的child的信号处理,需要在 Rust 中声明。
You use it by adding a struct member with the same name as one
idattribute in the template.
#[derive(CompositeTemplate, Default)]
#[template(resource = "/org/gtk_rs/example/window.ui")]
pub struct Window {
#[template_child]
pub button: TemplateChild<CustomButton>,
}
在XML文件中, signal 标签声明组件的信号的处理函数。
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
klass.bind_template_callbacks(); // 使用了 callback 就必须要有这一句
}
<object class="MyGtkAppCustomButton" id="button">
<signal name="clicked" handler="handle_button_clicked"/>
<property name="label">Press me!</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
</object>
而具体的回调函数在哪里定义?在 imp (模)块的结构体的 impl 中定义。
#[gtk::template_callbacks]
impl Window {
#[template_callback]
fn handle_button_clicked(button: &CustomButton) {
// Set the label to "Hello World!" after the button has been clicked on
button.set_label("Hello World!");
}
}
In order to access the widget's state we have to add
swapped="true"to thesignaltag.
要访问 XML 中定义 signal 标签的组件的状态,说直白一点,就是回调函数访问 self (ui文件对应的表达状态的结构体,impl Window 的 Window 就是 self),需要在 XML 中的 signal 中添加 swapped="true"。有了swapped="true" handle_button_clicked() 的第一个参数就可以是 self。
#[gtk::template_callbacks]
impl Window {
#[template_callback]
fn handle_button_clicked(&self, button: &CustomButton) {
let number_increased = self.number.get() + 1;
self.number.set(number_increased);
button.set_label(&number_increased.to_string())
}
}
在 Rust 中移除继承的 GTK widget 中的 Child,通过模板中的回调函数来访问组件中的child,而不在结构体中注册 template_child,。 class_init() 里必须添加 CustomButton::ensure_type();
Actions
app.set_accels_for_action("win.close", &["<Ctrl>W"]); 设置快捷键
创建一个 Action 不只是可以接收参数还可以设置状态。而状态也可以在设置的回调函数里面获取到,并且可以改变。 ActionEntry这里的activate接收的回调函数有 3 个参数。
// Add action "count" to `window` taking an integer as parameter
let action_count = ActionEntry::builder("count")
.parameter_type(Some(&i32::static_variant_type()))
.state(original_state.to_variant())
.activate(move |_, action, parameter| {
// Get state
let mut state = action
.state()
.expect("Could not get state.")
.get::<i32>()
.expect("The variant needs to be of type `i32`.");
// Get parameter
let parameter = parameter
.expect("Could not get parameter.")
.get::<i32>()
.expect("The variant needs to be of type `i32`.");
// Increase state by parameter and store state
state += parameter;
action.set_state(&state.to_variant());
// Update label with new state
label.set_label(&format!("Counter: {state}"));
})
.build();
用 Action 这里少了很多的绑定的操作,这里只声明按钮点击的信号处理和 Action。状态是直接将普通变量 to_variant() 转换一下,不需要声明一个属性,也不需要声明额外的信号处理不同的状态,可以直接在这个注册给 Action 的回调里面做。所以处理用户的输入不止是可以注册一个信号的处理,还可以将信号的处理再委托给 Action。
所有的 Button 都实现了 Actionable 接口,可以给 Button 指定 action_name(),action_target() 是指定 Action 的参数。
在 UI (xml)文件 内部可以指定 "action-name" 和 "action-target"。只需在 Rust 中构造组件后注册对应的 Action 即可。
<object class="GtkButton" id="button">
<property name="label">Press me!</property>
<property name="action-name">win.count</property>
<property name="action-target">1</property>
</object>
也就是说,读代码的时候看到 XML 文件里面有指定 action, 那么在 Rust 里面肯定是注册了这个Action处理函数的。
// setup_actions() in impl Window block of mod.rs
self.add_action_entries([action_count]);
// Trait shared by all GObjects
impl ObjectImpl for Window {
fn constructed(&self) {
// Call "constructed" on parent
self.parent_constructed();
// Add actions
self.obj().setup_actions();
}
}
覆盖父类的方法,在imp.rs的对应的trait的实现里实现覆盖的方法。
自定义组件方法,在mod.rs的模块的impl块里定义,自定义的 setup 的方法都在 constructed() 里面调用。
Menu
如果要创建菜单,就必须用 Action。一个菜单项符合3种描述:
- no parameter and no state, or
- no parameter and boolean state, or
- string parameter and string state.
menu 标签声明菜单的 model,在 GtkMenuButton 中指定菜单的 model, 菜单的具体行为是 action 去做的。
<template class="MyGtkAppWindow" parent="GtkApplicationWindow">
<property name="title">My GTK App</property>
+ <property name="width-request">360</property>
+ <child type="titlebar">
+ <object class="GtkHeaderBar">
+ <child type ="end">
+ <object class="GtkMenuButton">
+ <property name="icon-name">open-menu-symbolic</property>
+ <property name="menu-model">main-menu</property>
+ </object>
+ </child>
+ </object>
+ </child>
菜单可以很好的显示我们的有状态的action,菜单的action的状态需要通过设置来保存。
设置提供了一个 create_action 的方法可以很方便的创建 action.
GUI development with Rust and GTK4 阅读笔记的更多相关文章
- CI框架源码阅读笔记2 一切的入口 index.php
上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里再次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中, ...
- Mybatis3.3——源码阅读笔记
目录 Mybatis--Source阅读笔记 兵马未动,日志先行 异常 缓存 回收机制适配器 回收机制优化缓存 事务缓存 调试型缓存--日志缓存 解析 类型处理器 IO VFS Resource Re ...
- CI框架源代码阅读笔记2 一切的入口 index.php
上一节(CI框架源代码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程.这里再次贴出流程图.以备參考: 作为CI框架的入口文件.源代码阅读,自然由此開始. 在源代码阅读的 ...
- 《Java多线程编程实战指南(核心篇)》阅读笔记
<Java多线程编程实战指南(核心篇)>阅读笔记 */--> <Java多线程编程实战指南(核心篇)>阅读笔记 Table of Contents 1. 线程概念 1.1 ...
- 阅读笔记 1 火球 UML大战需求分析
伴随着七天国庆的结束,紧张的学习生活也开始了,首先声明,阅读笔记随着我不断地阅读进度会慢慢更新,而不是一次性的写完,所以会重复的编辑.对于我选的这本 <火球 UML大战需求分析>,首先 ...
- [阅读笔记]Software optimization resources
http://www.agner.org/optimize/#manuals 阅读笔记Optimizing software in C++ 7. The efficiency of differe ...
- 《uml大战需求分析》阅读笔记05
<uml大战需求分析>阅读笔记05 这次我主要阅读了这本书的第九十章,通过看这章的知识了解了不少的知识开发某系统的重要前提是:这个系统有谁在用?这些人通过这个系统能做什么事? 一般搞清楚这 ...
- <<UML大战需求分析>>阅读笔记(2)
<<UML大战需求分析>>阅读笔记(2)> 此次读了uml大战需求分析的第三四章,我发现这本书讲的特别的好,由于这学期正在学习设计模式这本书,这本书就讲究对uml图的利用 ...
- uml大战需求分析阅读笔记01
<<UML大战需求分析>>阅读笔记(1) 刚读了uml大战需求分析的第一二章,读了这些内容之后,令我深有感触.以前学习uml这门课的时候,并没有好好学,那时我认为这门课并没有什 ...
- Hadoop阅读笔记(七)——代理模式
关于Hadoop已经小记了六篇,<Hadoop实战>也已经翻完7章.仔细想想,这么好的一个框架,不能只是流于应用层面,跑跑数据排序.单表链接等,想得其精髓,还需深入内部. 按照<Ha ...
随机推荐
- win10安装MongoDB 5.0
1.首先去官网下载安装包:https://www.mongodb.com/try?tck=docs_navbar 2.安装过程一路下一步就行,选择complete安装,可以勾选安装Compass工具 ...
- 记录一次修复 JetBrains Rider 控制台输出乱码
在使用 JetBrains Rider 调试程序时,控制台输出日志出现了乱码. 歪打正着结果困扰许久的问题得到了解决,于是记录下了这个小短文. 具体的修复建议如下:将终端编码设置为 GB2312 具体 ...
- 2025AI应用元年,DeepSeek让领域小模型训练成本急剧下降!
关注公众号回复1 获取一线.总监.高管<管理秘籍> 模型训练俗称炼丹,而炼丹是修士特权,这就显得模型训练离普通人很远了. 虽然是笑谈,但如果对其中情况不太了解确实也会因为其背后深厚.复杂的 ...
- [SDOI2015] 序列统计 题解
乘法并不容易用 FFT 或 NTT 维护,考虑在模意义下化乘为加. 化乘为加主要有两种方法:\(\log\) 和 \(\gamma\)(指标),由于在取模意义下,所以使用后者. 那剩下的部分就是快速幂 ...
- sprintf用法详解
sprintf 将字串格式化. 在头文件 #include< stdio.h >中 语法: int sprintf(string format, mixed [args]...); 返回值 ...
- 探秘Transformer系列之(5)--- 训练&推理
探秘Transformer系列之(5)--- 训练&推理 0x00 概述 Transformer训练的目的是通过对输入源序列和模型输出序列的学习,来拟合真正的目标序列.推理的目的则是仅通过输入 ...
- Lombok 只会用@Setter @Getter @Data ? 老鸟带你玩转lombok
lombok的官网 官方网址 : https://projectlombok.org lombok 稳定特性文档:https://projectlombok.org/features/ lombok ...
- 雷电4扩展坞HDMI显示器无法睡眠问题
背景: 最近使用Dell的雷电4扩展坞WD22TB4,感觉很爽,取电脑时,不用再拔显示器.鼠标.键盘,直接把雷电4接口拔出即可. 后来通过windows update升级了intel显卡驱动后,发现电 ...
- docker - [15] springboot微服务打包docker镜像
步骤: 1.构建Springboot项目 2.打包应用 3.编写dockerfile 4.构建docker镜像 5.发布运行 一.构建Springboot项目 (1)创建一个SpringBoot(以下 ...
- c++常量引用,通过被引用变量修改数据无法同步到引用
正常情况下被引用的对象改变,常量引用的值也跟着改变.i和j是同一个对象,所以是同步的: int i = 42; const int& j = i; i = 43; cout << ...