Rust: Native Windows GUI 入门第一课,程序结构剖析

2023-05-16

基于派生宏的代码实例

Cargo.toml 文件

[package]
name = "demo"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
native-windows-gui = "*"
native-windows-derive = "*"

依赖两个外部包。我初步分析了实例代码后,猜测:

  • native-windows-gui:应该是 gui 的接口封装代码
  • native-windows-derive:应该封装了宏定义,类似 C++ 的 MFC 框架,以便 Rust 自动生成相关代码。

main.rs 文件

extern crate native_windows_gui as nwg;
extern crate native_windows_derive as nwd;

use nwd::NwgUi;
use nwg::NativeUi;


#[derive(Default, NwgUi)]
pub struct BasicApp {
    #[nwg_control(size: (300, 115), position: (300, 300), title: "Basic example", flags: "WINDOW|VISIBLE")]
    #[nwg_events( OnWindowClose: [BasicApp::say_goodbye] )]
    window: nwg::Window,

    #[nwg_control(text: "Heisenberg", size: (280, 25), position: (10, 10))]
    name_edit: nwg::TextInput,

    #[nwg_control(text: "Say my name", size: (280, 60), position: (10, 40))]
    #[nwg_events( OnButtonClick: [BasicApp::say_hello] )]
    hello_button: nwg::Button
}

impl BasicApp {
    fn say_hello(&self) {
        nwg::simple_message("Hello", &format!("Hello {}", self.name_edit.text()));
    }
    fn say_goodbye(&self) {
        nwg::simple_message("Goodbye", &format!("Goodbye {}", self.name_edit.text()));
        nwg::stop_thread_dispatch();
    }
}

fn main() {
    nwg::init().expect("Failed to init Native Windows GUI");
    let _app = BasicApp::build_ui(Default::default()).expect("Failed to build UI");
    nwg::dispatch_thread_events();
}

NwgUi

观察代码中的 #[derive(Default, NwgUi)],可以断定, NwgUi 是用于自动生成代码的宏。由于 Rust 的宏定义和 C++ 相比具有碾压式的优势,Rust 提供了完备的语法机制进行宏定义的编码,因此,这套库的易用性和性能肯定远远由于 MFC 之类的框架。

控件

看下面的代码:

    #[nwg_control(size: (300, 115), position: (300, 300), title: "Basic example", flags: "WINDOW|VISIBLE")]
    #[nwg_events( OnWindowClose: [BasicApp::say_goodbye] )]
    window: nwg::Window,

定义了字段 window: nwg::Windows,这个代码应该在 native_windows_gui 包中。

  • 控件属性:宏定义 #[nwg_control(size: (300, 115), position: (300, 300), title: "Basic example", flags: "WINDOW|VISIBLE")] 定义了属性
  • 事件关联:宏定义 #[nwg_events( OnWindowClose: [BasicApp::say_goodbye] )] 定义了事件与回调函数之间的关联。注意回调函数放在了 [...] 中,这说明事件可以同时关联多个回调函数。

看到这里,初步判断这套 GUI 框架的基本模型与 Delphi 的 PME (Property、Method、Event)模型基本是一致的。我觉得这是好事,因为这意味这套 GUI 架构秉承了 Delphi 简洁明了的风格。我自己觉得,微软在 WinForm 和 WPF 里搞的那些新玩意儿,把简单问题复杂化了。还是把自主权交给程序员,不要越俎代庖,画蛇添足。

事件从何而来?

代码中的事件名称,例如 OnWindowClose 搞得我一头雾水,不知从何而来。查看了开源代码,也没找到定义。这件事情先放一放。我们接下来看看如果不采用宏定义,代码如何编写,或许从中能找到一些线索。

不用派生宏的代码编写方式

main.rs 文件

直接上代码:

extern crate native_windows_gui as nwg;
use nwg::NativeUi;

#[derive(Default)]
pub struct BasicApp {
    window: nwg::Window,
    name_edit: nwg::TextInput,
    hello_button: nwg::Button,
}

impl BasicApp {
    fn say_hello(&self) {
        nwg::simple_message("Hello", &format!("Hello {}", self.name_edit.text()));
    }
    fn say_goodbye(&self) {
        nwg::simple_message("Goodbye", &format!("Goodbye {}", self.name_edit.text()));
        nwg::stop_thread_dispatch();
    }
}

//
// ALL of this stuff is handled by native-windows-derive
//
mod basic_app_ui {
    use super::*;
    use native_windows_gui as nwg;
    use std::cell::RefCell;
    use std::ops::Deref;
    use std::rc::Rc;

    pub struct BasicAppUi {
        inner: Rc<BasicApp>,
        default_handler: RefCell<Option<nwg::EventHandler>>,
    }

    impl nwg::NativeUi<BasicAppUi> for BasicApp {
        fn build_ui(mut data: BasicApp) -> Result<BasicAppUi, nwg::NwgError> {
            use nwg::Event as E;
            // Controls
            nwg::Window::builder()
                .flags(nwg::WindowFlags::WINDOW | nwg::WindowFlags::VISIBLE)
                .size((300, 115))
                .position((300, 300))
                .title("Basic example")
                .build(&mut data.window)?;

            nwg::TextInput::builder()
                .size((280, 25))
                .position((10, 10))
                .text("Heisenberg")
                .parent(&data.window)
                .focus(true)
                .build(&mut data.name_edit)?;

            nwg::Button::builder()
                .size((280, 60))
                .position((10, 40))
                .text("Say my name")
                .parent(&data.window)
                .build(&mut data.hello_button)?;

            // Wrap-up
            let ui = BasicAppUi {
                inner: Rc::new(data),
                default_handler: Default::default(),
            };

            // Events
            let evt_ui = Rc::downgrade(&ui.inner);
            let handle_events = move |evt, _evt_data, handle| {
                if let Some(ui) = evt_ui.upgrade() {
                    match evt {
                        E::OnButtonClick => {
                            if &handle == &ui.hello_button {
                                BasicApp::say_hello(&ui);
                            }
                        }
                        E::OnWindowClose => {
                            if &handle == &ui.window {
                                BasicApp::say_goodbye(&ui);
                            }
                        }
                        _ => {}
                    }
                }
            };

            *ui.default_handler.borrow_mut() = Some(nwg::full_bind_event_handler(
                &ui.window.handle,
                handle_events,
            ));

            return Ok(ui);
        }
    }

    impl Drop for BasicAppUi {
        /// To make sure that everything is freed without issues, the default handler must be unbound.
        fn drop(&mut self) {
            let handler = self.default_handler.borrow();
            if handler.is_some() {
                nwg::unbind_event_handler(handler.as_ref().unwrap());
            }
        }
    }

    impl Deref for BasicAppUi {
        type Target = BasicApp;

        fn deref(&self) -> &BasicApp {
            &self.inner
        }
    }
}

fn main() {
    nwg::init().expect("Failed to init Native Windows GUI");
    nwg::Font::set_global_family("Segoe UI").expect("Failed to set default font");
    let _ui = BasicApp::build_ui(Default::default()).expect("Failed to build UI");
    nwg::dispatch_thread_events();
}

BasicApp 定义

我觉得 BasicApp 的定义相当精炼:

#[derive(Default)]
pub struct BasicApp {
    window: nwg::Window,
    name_edit: nwg::TextInput,
    hello_button: nwg::Button,
}
impl BasicApp {
    fn say_hello(&self) {
        nwg::simple_message("Hello", &format!("Hello {}", self.name_edit.text()));
    }
    fn say_goodbye(&self) {
        nwg::simple_message("Goodbye", &format!("Goodbye {}", self.name_edit.text()));
        nwg::stop_thread_dispatch();
    }
}

控件属性定义

控件属性的构建代码:

 fn build_ui(mut data: BasicApp) -> Result<BasicAppUi, nwg::NwgError> {
            use nwg::Event as E;
            // Controls
            nwg::Window::builder()
                .flags(nwg::WindowFlags::WINDOW | nwg::WindowFlags::VISIBLE)
                .size((300, 115))
                .position((300, 300))
                .title("Basic example")
                .build(&mut data.window)?;

            nwg::TextInput::builder()
                .size((280, 25))
                .position((10, 10))
                .text("Heisenberg")
                .parent(&data.window)
                .focus(true)
                .build(&mut data.name_edit)?;

用级联模式为控件的属性逐个赋值,设计模式很有启发性。看看 VSCode 里带语法提示的编辑截屏:
在这里插入图片描述

Build 的每个 属性set方法的返回结果都是 Build,最后一个 build 方法输出结果。代码看上去很舒服,不知道效率如何。估计这个设计模式,牺牲了运行效率,换取了代码的可读性。查看了一下WindowBuilder源代码:

    pub fn flags(mut self, flags: WindowFlags) -> WindowBuilder<'a> {
        self.flags = Some(flags);
        self
    }

如果编译器不能自动优化的话,每次都要把 WindowBuilder 复制一下,这效率堪忧呀!希望 Rust 的惰性求值机制能帮助编译器自动优化代码。

事件关联

代码如下:

// Events
            let evt_ui = Rc::downgrade(&ui.inner);
            let handle_events = move |evt, _evt_data, handle| {
                if let Some(ui) = evt_ui.upgrade() {
                    match evt {
                        E::OnButtonClick => {
                            if &handle == &ui.hello_button {
                                BasicApp::say_hello(&ui);
                            }
                        }
                        E::OnWindowClose => {
                            if &handle == &ui.window {
                                BasicApp::say_goodbye(&ui);
                            }
                        }
                        _ => {}
                    }
                }
            };

基于这套代码,顺藤摸瓜就找到了事件定义。文件 events.rs 定义了事件的枚举类型。至于事件与回调函数具体的关联方式,这里不细究了,感觉搞明白不容易,也没啥用。

实际编程,还是借助派生宏比较省事。上面这个存手工编码,还是有些麻烦。派生宏的作用,估计也是为了生成这些代码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Rust: Native Windows GUI 入门第一课,程序结构剖析 的相关文章

  • Authentication plugin ‘caching_sha2_password‘ 服务端也无法连接问题彻底解决

    在网上搜索了很多的帖子 xff0c 发现描述的都是外部客户端无法登录到mysql上 xff0c 登录上服务器以后连接更改配置的方式 xff0c 但是 xff01 xff01 xff01 xff01 xff01 我现在是服务器连接也报错啊啊啊
  • Hexo分类及标签显示

    Hexo根目录配置 config yml category map Blogs categories Blogs Tech categories Tech Tools categories Tools Other categories Ot
  • IDEA查看历史记录

    方法一 文件内 Ctrl 43 右键 Local History Show History xff0c 显示当前文件的本地修改历史 方法二 一 xff1a 在文件内 xff0c 按 Ctrl 43 Shift 43 A 弹出全部搜索对话框
  • SpringBoot-JPA整合ShardingShpere自定义分布式主键

    分布式主键简介 在分布式环境下 xff0c 由于分库分表导致数据水平拆分后无法使用单表自增主键 xff0c 因此我们需要一种全局唯一id生成策略作为分布式主键 当前有如下解决方案 UUID xff08 Universally Unique
  • Gitlab的安装与配置

    安装开始时 xff0c 需确认服务器最小配置是2核4G xff0c 因为gitlab软件比较大 1 配置yum源 xff1a vim etc yum repos d gitlab repo gitlab name 61 gitlab ce
  • Error creating bean with name ‘org.springframework.aop.aspectj.AspectJPointcutAdvisor#0

    问题 xff1a nested exception is org springframework beans factory BeanCreationException Error creating bean with name 39 or
  • Vue前端项目开发页面(二)

    前端界面开发 开发工具版本 64 vue cli 4 5 13 新建Login vue登陆页 1 在 vue exemples 项目 xff0c 选中components目录右键 New Vue Component xff0c 名称为 Lo
  • SpringBoot整合WebSocket

    概述 HTTP 协议是一种无状态的 无连接的 单向的应用层协议 它采用了请求 响应模型 通信请求只能由客户端发起 xff0c 服务端对请求做出应答处理 WebSocket和HTTP一样 xff0c 都是一种网络通信协议 比起HTTP只能由客
  • SpringBoot整合MybatisPlus使用IPage实现分页

    概述 MybatisPlus 提供了分页的功能 IPage内部原理是基于拦截器 xff0c 但是这个拦截的是方法以及方法中的参数 xff0c 这个也会判断是否是查询操作 如果是查询操作 xff0c 才会进入分页的处理逻辑 进入分页逻辑处理后
  • SpringBoot统一异常处理

    概述 SpringBoot 提供了 64 ControllerAdvice 64 RestControllerAdvice 注解可以实现统一异常处理 xff0c 只需要在定义异常类加上以上注解即可 自定义异常处理 定义统一异常处理 span
  • 萌新学习算法——并查集基础

    并查集 在算法设计中 xff0c 将一个集合和另外一个集合合并时 xff0c 就会用到并查集 假如不用并查集 xff0c 你可能会用到集合和列表来实现 xff0c 这样会使代码看起来很复杂 xff0c 而且执行效率不高 xff0c 下面用洛
  • linux中断及其底半部-s5p6818开发平台

    中断分为两个部分 xff1a 中断顶部 xff08 top half xff09 和中断底半部 xff08 bootom half xff09 一 中断顶部 xff08 top half xff09 中断上半部需要处理一下三种情况 xff1
  • Windows如何查看.db数据库文件

    从android应用导出的 db文件 xff0c 想在Windows电脑端看 xff0c 可以用SQLite Expert Professional这个软件查看 xff0c 网上说用FireFox的插件sqlite manager xff0
  • Ubuntu工具-01 UEX

    UltraEdit是Windows旗下一款流行的老牌文本 HEX编辑器 xff08 非开源 xff09 UltraEdit正被移植到Linux平台 该移植名为UEX xff0c 意即UltraEdit for Linux UltraEdit
  • Ubuntu工具-2 OBS Studio

    文章目录 1 下载并安装1 1 Flathub安装1 2 Snap安装1 3 PPA源方式安装1 3 1 检查OpenGL版本 xff0c 其版本必须高于 96 3 3 96 1 3 2 安装虚拟摄像机驱动1 3 3 安装ffmpeg库1
  • Docker build创建指定容器镜像

    Docker build xff1a Build an image from a Dockerfile 按照Dockerfile文件所定义内容创建临时性容器 xff0c 把Docker中所定义的每行命令在临时容器中执行 xff0c 然后生成
  • Ubuntu工具-03 VLC

    文章目录 1 安装VLC Media Player的方法1 1 apt安装1 2 snap安装 xff08 未测试 xff09 2 启动VLC Media Player并设置为默认媒体播放器 VLC Media Player xff08 V
  • MySQL笔记-07 常用函数

    文章目录 1 数学函数1 1 ABS1 2 CEIL和CEILIN1 3 FLOOR1 4 MOD1 5 ROUND1 6 TRUNCATE 2 字符串函数2 1 CONCAT2 2 CONCAT WS2 3 INSERT2 4 LOWER
  • SpringMVC-01 Web基础介绍

    文章目录 1 CGI1 1 CGI原理1 2 输入 出1 3 环境变量1 3 1 与请求相关的环境变量1 3 2 与服务器相关的环境变量1 3 3 与客户端相关的环境变量1 3 4 详细说明1 3 4 1 REQUEST METHOD 1
  • SpringMVC-02 MVC模式介绍

    文章目录 1 Java Web开发模型2 JSP 43 JavaBean开发模型 xff08 model1 xff09 3 MVC开发模式 xff08 model2 xff09 3 1 MVC模式基础3 1 1 模型 视图 控制器各部分的作

随机推荐

  • 系统架构师-科目1考点

  • 系统架构师-科目2考点

  • 系统架构师-科目3考点

  • Hive笔记-01 架构概述

    文章目录 1 概述2 Metadata Metastore的作用3 Metastore三种配置方式3 1 Hive配置参数说明3 1 1 基本配置参数3 1 2 其他配置参数 3 2 内嵌模式 xff08 Embedded xff09 3
  • Hadoop笔记-01概述

    文章目录 1 什么是大数据 xff1f 1 1 大数据计算模式及代表产品1 2 云计算与物联网1 2 1 云计算1 2 1 1 虚拟化1 2 1 2 分布式存储1 2 1 3 分布式计算1 2 1 4 多租户 1 3 物联网1 3 1 识别
  • Hadoop笔记-02 安装

    文章目录 1 VBOX安装CentOS71 1 安装VBOX软件1 2 下载CentOS7镜像文件1 3 初始化VBOX虚拟盘1 4 CentOS7网络配置1 5 CentOS7 yum源配置1 6 CentOS7 一般配置1 6 1关闭防
  • ffmpeg播放器实现详解 - 视频同步控制

    1 时间戳 时间戳的概念贯穿音视频开发始终 xff0c 重要性不言而喻 时间戳告诉我们在什么时候 xff0c 用多快的速度去播哪一帧 xff0c 其中 xff0c DTS decoding timestamp 告诉我们何时解码 xff0c
  • Ubuntu22.04 安装深度微信报错 依赖: libsasl2-2 (>= 2.1.27.1)

    现象 xff1a span class token punctuation span base span class token punctuation span pang 64 pang HP span class token funct
  • Ubuntu22.04更新后 点击深度微信无反应

    系统版本 xff1a Ubuntu 22 04 jammy内核 xff1a x86 64 Linux 5 15 0 53 genericdeepin wine6 stable 版本 xff1a 6 0 0 41 1 深度微信图标点击后 xf
  • MySQL笔记-08 索引

    文章目录 1 索引概述1 1 MySQL索引分类1 1 1 普通索引1 1 2 唯一性索引1 1 3 全文索引1 1 4 单列索引1 1 5 多列索引1 1 6 空间索引 2 创建索引2 1 在建立数据表时创建索引2 1 1 普通索引创建2
  • MySQL笔记-09 视图

    文章目录 1 视图概念1 1 概念1 2 作用 2 创建视图2 1 查看创建视图的权限2 2 创建视图的步骤2 3 创建视图的注意事项 3 视图操作3 1 查看视图3 1 1 DESCRIBE语句3 1 2 SHOW TABLE STATU
  • MySQL笔记-10 数据完整性约束

    文章目录 1 定义完整性约束1 1 实体完整性1 1 1 主键约束1 1 2 候选键约束 1 2 参照完整性1 3 用户定义完整性1 3 1 非空约束1 3 2 CHECK约束1 3 2 1 对列实施CHECK约束1 3 2 2 对表实施C
  • Linux命令行笔记-00 综述

    文章目录 1 Linux命令行简介1 1 Linux命令行的分类1 1 1 根据系统中作用来分类1 1 2 根据对象来分类 2 Linux命令行解释器2 1 命令行解释器shell2 1 1 核心程序2 1 2 公用程序shell2 1 3
  • Linux命令行笔记-01 文件管理-文件的建立、移动和删除

    文章目录 1 文件的建立 移动和删除1 1 96 cat 96 建立文件1 1 1 语法格式与参数1 1 2 示例 1 2 96 touch 96 建立文件1 2 1 语法格式与参数1 2 2 示例1 2 3 注意 1 3 96 ln 96
  • CMake学习-01 综述

    文章目录 1 CMake1 1 CMake生成makefile并编译的流程 2 CMakeLists txt2 1 Demo讲解2 2 常用命令2 2 1 指定CMAKE的最低版本2 2 2 设置项目名称2 2 3 设置变量2 2 4 设置
  • Rust:官方迭代器大全

    一 for 和迭代器 先看一段代码 xff1a span class token keyword fn span span class token function definition function main span span cl
  • Rust: 函数的重载——我做的的一组小实验

    编程的时候 xff0c 我发现有不少函数能够根据左值类型自动调用重载函数 但是 xff0c 我知道 Rust 的函数是不支持重载的 所以我打算尝试一下这一 重载 现象是如何实现的 一 Rust 不支持函数重载 写一段代码 xff1a spa
  • php产生大量session文件导致报错无法创建修改文件:no space left on device

    阿里云SLB健康检测后端服务器组产生百万级别的php的0k大小session文件 今天早上在登录公司一台阿里云的服务器上vim修改配置文件以及touch文件时报错 xff1a no space left on device df h 查看了
  • Rust: Native Windows GUI下载、安装、演示入门

    上 github 下载 xff0c 网址为 https github com gabdube native windows gui 上面有安装说明 按说明方法 xff0c 老是提示权限不够 配置了 ssh 公钥证书 xff0c 仍然不行 请
  • Rust: Native Windows GUI 入门第一课,程序结构剖析

    基于派生宏的代码实例 Cargo toml 文件 span class token punctuation span package span class token punctuation span name span class tok