#desktop-applications #gui-applications #winapi #gui #ui #api-wrapper #windows

native-windows-gui

一个用于在Microsoft Windows桌面开发原生GUI应用程序的Rust库。Native-windows-gui通过Rust风格的API封装了原生的win32窗口控件

17个版本 (稳定)

1.0.13 2022年9月5日
1.0.12 2021年7月5日
1.0.11 2021年4月3日
1.0.10 2021年1月27日
0.1.1 2016年11月21日

#4 in Windows API

Download history 554/week @ 2024-04-22 549/week @ 2024-04-29 784/week @ 2024-05-06 1037/week @ 2024-05-13 1131/week @ 2024-05-20 1433/week @ 2024-05-27 2151/week @ 2024-06-03 1895/week @ 2024-06-10 1981/week @ 2024-06-17 2840/week @ 2024-06-24 3168/week @ 2024-07-01 3144/week @ 2024-07-08 3478/week @ 2024-07-15 4209/week @ 2024-07-22 3481/week @ 2024-07-29 3336/week @ 2024-08-05

14,712每月下载量
11 crates中使用

MIT许可协议

1MB
20K SLoC

Native Windows GUI

欢迎来到Native Windows GUI(简称NWG)。这是一个用于在Microsoft Windows桌面开发原生GUI应用程序的Rust库。

NWG是对WINAPI的一个非常轻量级的包装。它通过提供一个简单、安全且类似Rust的接口,允许您,作为开发者,处理API的怪癖和粗糙边缘。

Native Windows GUI使事情保持简单。这意味着编译时间短,资源使用量少,查找文档时间更短,您有更多时间开发应用程序。

当然,您不必仅凭我的话,请查看展示示例

这是NWG的第三版也是最终版。它被认为是“成熟”的,或者说,用我的话说,“待办事项列表为空,它很可能会保持这种状态”。这个版本实现了在Windows上开发应用程序所需的大部分内容。请勿使用旧版本,因为它们有“不可调和的设计决策”,并且无法支持一些关键功能。未来的开发将在其他库中进行。

如果您已经阅读了这篇介绍,您应该知道我的Twitter用户名是#gdube_dev,您可以通过GitHub Sponsors来支持这个项目。

任何支持都将受到极大的欢迎。

安装

要在项目中使用NWG,请将其添加到cargo.toml中

[dependencies]
native-windows-gui = "1.0.12"
native-windows-derive = "1.0.3" # Optional. Only if the derive macro is used.

然后在main.rs或lib.rs中

extern crate native_windows_gui as nwg;
extern crate native_windows_derive as nwd;  // Optional. Only if the derive macro is used.

Rust 2018别名

您可以通过在Cargo.toml中添加以下代码来跳过源代码中的extern crate定义。注意,过程宏仍然需要一个extern crate定义,所以这不会与native-windows-derive一起工作。

[dependencies]
nwg = {version = "^1.0.12", package = "native-windows-gui"}

尝试一下

自己看看。NWG有很多示例和一个完全交互的测试套件。您需要做的只是

git clone [email protected]:gabdube/native-windows-gui.git

cd native-windows-gui/native-windows-gui # Running the tests from the workspace screws up the features

cargo test everything --features "all"  # For the test suite
cargo run --example basic
cargo run --example calculator
cargo run --example message_bank
cargo run --example image_decoder_d --features "extern-canvas"
cargo run --example partials --features "listbox frame combobox"
cargo run --example system_tray --features "tray-notification message-window menu cursor"
cargo run --example dialog_multithreading_d --features "notice"
cargo run --example image_decoder_d --features "image-decoder file-dialog"
cargo run --example month_name_d --features "winnls textbox"
cargo run --example splash_screen_d --features "image-decoder"
cargo run --example drop_files_d --features "textbox"

cd examples/opengl_canvas
cargo run

# The closest thing to a real application in the examples
cd ../examples/sync-draw
cargo run

# Requires the console to be run as Admin because of the embed resource
cd ../examples/embed_resources
cargo run

从Ubuntu交叉编译

需求:MinGW编译器

sudo apt install gcc-mingw-w64-x86-64

需求:Rust支持

rustup target add x86_64-pc-windows-gnu

编译和运行基本示例

cargo build --release --target=x86_64-pc-windows-gnu
cargo build --release --target=x86_64-pc-windows-gnu --example basic
wine target/x86_64-pc-windows-gnu/release/examples/basic.exe

支持的功能

  • 完整的winapi控制库(参考)
    • 一些非常特殊的功能不支持:平面滚动条、IP控件、Rebar和翻页器。
  • 菜单和菜单栏
  • 图像和字体资源
    • BMP
    • ICO
    • CUR
    • PNG*
    • GIF*
    • JPG*
    • TIFF*
    • DDS*
    • *:Windows Imaging Component (WIC) 扩展图像格式。
  • 本地化支持
    • 内部使用Windows国家语言支持(参考)
  • 工具提示
  • 系统托盘通知
  • 光标处理
  • 完整的剪贴板包装器
  • 部分模板支持
    • 将大型应用程序分割成块
  • 动态控件支持
    • 在运行时添加/删除控件
    • 在运行时绑定或解绑新事件
  • 多线程应用程序支持
    • 从其他线程与GUI线程通信
    • 在不同的线程上运行多个窗口
  • 简单的布局配置
    • FlexboxLayout
    • GridLayout
  • 拖放
    • 从桌面拖放文件到窗口
  • 最常用的对话框
    • 文件对话框(保存、打开、打开文件夹)
    • 字体对话框
    • 颜色对话框
  • 外部渲染API可以使用的画布
  • 高DPI感知
  • 对辅助功能功能的支持
    • 标签导航
  • 支持低级别系统消息捕获(HWND、MSG、WPARAM、LPARAM)
  • 使用Wine和mingw从Linux交叉编译和测试到Windows。
    • 并非所有功能都受支持(但大多数都支持,感谢WINE!)
    • 有关步骤,请参阅https://zork.net/~st/jottings/rust-windows-and-debian.html

性能

这是在一个Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz, 3401 Mhz, 4 Core(s), 8 Logical Processor(s)上测量的。

在发布模式下,基本示例在磁盘上占用163kb,在内存中占用900kb。启动时间是瞬间的。

交互式测试套件(包含每个功能和100多个测试)在磁盘上占用931 kb,在内存中占用8MB。启动时间仍然是瞬间的。

基本应用程序的初始构建时间大约需要22秒。这主要是由于winapi-rs初始编译时间。后续编译时间大约为0.7秒

开发

这个库的开发被认为是“完成”的。我的意思是,API 不会有任何变化。如果发现错误或者在文档的某些区域不清楚,可以提出问题。如果忽略了某个非常重要的功能,它很可能会被添加。

许可证

NWG 使用 MIT 许可证

代码示例

使用原生 Windows 派生

#![windows_subsystem = "windows"]
/*!
    A very simple application that shows your name in a message box.
    Unlike `basic_d`, this example uses layout to position the controls in the window
*/


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_layout(parent: window, spacing: 1)]
    grid: nwg::GridLayout,

    #[nwg_control(text: "Heisenberg", focus: true)]
    #[nwg_layout_item(layout: grid, row: 0, col: 0)]
    name_edit: nwg::TextInput,

    #[nwg_control(text: "Say my name")]
    #[nwg_layout_item(layout: grid, col: 0, row: 1, row_span: 2)]
    #[nwg_events( OnButtonClick: [BasicApp::say_hello] )]
    hello_button: nwg::Button
}

impl BasicApp {

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

}

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 _app = BasicApp::build_ui(Default::default()).expect("Failed to build UI");
    nwg::dispatch_thread_events();
}

基本示例。适用于只需要简单的静态 UI 的情况

#![windows_subsystem = "windows"]
/**
    A very simple application that show your name in a message box.

    This demo shows how to use NWG without the NativeUi trait boilerplate.
    Note that this way of doing things is alot less extensible and cannot make use of native windows derive.

    See `basic` for the NativeUi version and `basic_d` for the derive version
*/
extern crate native_windows_gui as nwg;
use std::rc::Rc;

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 mut window = Default::default();
    let mut name_edit = Default::default();
    let mut hello_button = Default::default();
    let layout = Default::default();

    nwg::Window::builder()
        .size((300, 115))
        .position((300, 300))
        .title("Basic example")
        .build(&mut window)
        .unwrap();

    nwg::TextInput::builder()
        .text("Heisenberg")
        .focus(true)
        .parent(&window)
        .build(&mut name_edit)
        .unwrap();

    nwg::Button::builder()
        .text("Say my name")
        .parent(&window)
        .build(&mut hello_button)
        .unwrap();

    nwg::GridLayout::builder()
        .parent(&window)
        .spacing(1)
        .child(0, 0, &name_edit)
        .child_item(nwg::GridLayoutItem::new(&hello_button, 0, 1, 1, 2))
        .build(&layout)
        .unwrap();

    let window = Rc::new(window);
    let events_window = window.clone();

    let handler = nwg::full_bind_event_handler(&window.handle, move |evt, _evt_data, handle| {
        use nwg::Event as E;

        match evt {
            E::OnWindowClose => 
                if &handle == &events_window as &nwg::Window {
                    nwg::modal_info_message(&events_window.handle, "Goodbye", &format!("Goodbye {}", name_edit.text()));
                    nwg::stop_thread_dispatch();
                },
            E::OnButtonClick => 
                if &handle == &hello_button {
                    nwg::modal_info_message(&events_window.handle, "Hello", &format!("Hello {}", name_edit.text()));
                },
            _ => {}
        }
    });

    nwg::dispatch_thread_events();
    nwg::unbind_event_handler(&handler);
}

使用 NativeUi 模板

#![windows_subsystem = "windows"]
/*!
    A very simple application that shows your name in a message box.
    Uses layouts to position the controls in the window
*/

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


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

impl BasicApp {

    fn say_hello(&self) {
        nwg::modal_info_message(&self.window, "Hello", &format!("Hello {}", self.name_edit.text()));
    }
    
    fn say_goodbye(&self) {
        nwg::modal_info_message(&self.window, "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 native_windows_gui as nwg;
    use super::*;
    use std::rc::Rc;
    use std::cell::RefCell;
    use std::ops::Deref;

    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()
                .text("Heisenberg")
                .parent(&data.window)
                .focus(true)
                .build(&mut data.name_edit)?;

            nwg::Button::builder()
                .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));

           // Layouts
           nwg::GridLayout::builder()
            .parent(&ui.window)
            .spacing(1)
            .child(0, 0, &ui.name_edit)
            .child_item(nwg::GridLayoutItem::new(&ui.hello_button, 0, 1, 1, 2))
            .build(&ui.layout)?;

            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();
}

致谢

对于测试套件中使用的图标(仅限那里)

依赖项

~0–2MB
~37K SLoC