基于派生宏的代码实例
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 定义了事件的枚举类型。至于事件与回调函数具体的关联方式,这里不细究了,感觉搞明白不容易,也没啥用。
实际编程,还是借助派生宏比较省事。上面这个存手工编码,还是有些麻烦。派生宏的作用,估计也是为了生成这些代码。