简记

这是我第二次从头开始阅读,有第一次的印象要容易不少。

如果只关心具体的做法,而不思考为什么这样做,以及整体的框架,阅读的过程将会举步维艰。

简略记录 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 structinstance 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 id attribute 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 the signal tag.

要访问 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 阅读笔记的更多相关文章

  1. CI框架源码阅读笔记2 一切的入口 index.php

    上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里再次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中, ...

  2. Mybatis3.3——源码阅读笔记

    目录 Mybatis--Source阅读笔记 兵马未动,日志先行 异常 缓存 回收机制适配器 回收机制优化缓存 事务缓存 调试型缓存--日志缓存 解析 类型处理器 IO VFS Resource Re ...

  3. CI框架源代码阅读笔记2 一切的入口 index.php

    上一节(CI框架源代码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程.这里再次贴出流程图.以备參考: 作为CI框架的入口文件.源代码阅读,自然由此開始. 在源代码阅读的 ...

  4. 《Java多线程编程实战指南(核心篇)》阅读笔记

    <Java多线程编程实战指南(核心篇)>阅读笔记 */--> <Java多线程编程实战指南(核心篇)>阅读笔记 Table of Contents 1. 线程概念 1.1 ...

  5. 阅读笔记 1 火球 UML大战需求分析

    伴随着七天国庆的结束,紧张的学习生活也开始了,首先声明,阅读笔记随着我不断地阅读进度会慢慢更新,而不是一次性的写完,所以会重复的编辑.对于我选的这本   <火球 UML大战需求分析>,首先 ...

  6. [阅读笔记]Software optimization resources

    http://www.agner.org/optimize/#manuals 阅读笔记Optimizing software in C++   7. The efficiency of differe ...

  7. 《uml大战需求分析》阅读笔记05

    <uml大战需求分析>阅读笔记05 这次我主要阅读了这本书的第九十章,通过看这章的知识了解了不少的知识开发某系统的重要前提是:这个系统有谁在用?这些人通过这个系统能做什么事? 一般搞清楚这 ...

  8. <<UML大战需求分析>>阅读笔记(2)

    <<UML大战需求分析>>阅读笔记(2)> 此次读了uml大战需求分析的第三四章,我发现这本书讲的特别的好,由于这学期正在学习设计模式这本书,这本书就讲究对uml图的利用 ...

  9. uml大战需求分析阅读笔记01

    <<UML大战需求分析>>阅读笔记(1) 刚读了uml大战需求分析的第一二章,读了这些内容之后,令我深有感触.以前学习uml这门课的时候,并没有好好学,那时我认为这门课并没有什 ...

  10. Hadoop阅读笔记(七)——代理模式

    关于Hadoop已经小记了六篇,<Hadoop实战>也已经翻完7章.仔细想想,这么好的一个框架,不能只是流于应用层面,跑跑数据排序.单表链接等,想得其精髓,还需深入内部. 按照<Ha ...

随机推荐

  1. Linux环境下安装phantomjs

    一.创建文件夹,用来存放软件 cd /opt/softWare mkdir  phantomJS cd phantomJS 二.下载并解压 wget https://bitbucket.org/ari ...

  2. SWD下载口的端口状态

    1.关于SWD SWD下载口的端口状态:SWD为上拉,SWC为下拉. SWD是MCU下载程序和调试的端口,分为四线制和五线制 四线制:VCC GND SWDIO SWCKL 五线制:VCC GND S ...

  3. 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?

    你好呀,我是歪歪. 事情是这样的,前几天有一个读者给我发消息,说他面试的时候遇到一个奇形怪状的面试题. 歪师傅纵横面试界多年,最喜欢的是奇形怪状的面试题. 可以说是见过大场面的人,所以让他描述一下具体 ...

  4. macbookpro m3本地部署DeepSeek模型

    macbookpro m3有着十分强大的性能.在deepseek如火如荼的当下,可以尝试在本地部署并使用.还可以将自己的文档作为语料喂给deepseek,使其能成为自己专属的AI助手. 本文介绍使用o ...

  5. 植物大战僵尸杂交版,最新安装包(PC+手机+苹果)+ 修改器+高清工具

    植物大战僵尸杂交版:全新游戏体验与创意碰撞 游戏简介 <植物大战僵尸杂交版>是由B站知名UP主潜艇伟伟迷基于经典游戏<植物大战僵尸>进行的一次大胆且富有创意的二次创作.这款游戏 ...

  6. SQLSugar 支持 TDengine 超级表的使用指南

    TDengine 是一款高性能.分布式的时序数据库,广泛应用于物联网.工业互联网等领域.其核心概念之一是超级表(Super Table),它类似于传统数据库中的表结构模板,允许用户通过标签(Tag)动 ...

  7. php禁止跨域调用api(来自文心快码)

    在PHP中,禁止跨域调用API通常涉及到设置正确的HTTP响应头,以告知浏览器不允许来自不同源的请求.跨域资源共享(CORS)是一个W3C标准,它允许服务器放宽同源策略(SOP),从而允许某些跨站请求 ...

  8. go generate

    介绍 go generate 命令是go 1.4版本里面新添加的一个命令,当运行 go generate 时,它将扫描与当前包相关的源代码文件,找出所有包含 //go:generate 的特殊注释,提 ...

  9. 一个属性同时使用Autowired和Resource注解会发生什么?

    首发于公众号:BiggerBoy 右侧图片wx扫码关注有惊喜 欢迎关注,查看更多技术文章 如题,如果在同一个属性上使用@Autowired注解注入bean1,然后使用@Resource注解注入bean ...

  10. spring 事务失效的 12 种场景

    看这个:https://blog.csdn.net/hanjiaqian/article/details/120501741里面有12种失效场景以及如何解决. 在 spring 中为了支持编程式事务, ...