In this first chapter, you will install NWG and then look at a very simple application. This tutorial will use both native-windows-derive (the procedural macro to generate GUI's from rust structs) and the plain native-window-gui api. The next chapter "Basics" will go deeper into these.
cargo.toml
:
[dependencies] native-windows-gui = "1.0.12" native-windows-derive = "1.0.3"And then, in
main.rs
or lib.rs
:
extern crate native_windows_gui as nwg; extern crate native_windows_derive as nwd;
/*!
A very simple application that show your name in a message box.
See `basic` for the version without the derive macro
*/
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();
}
Default
trait. Default-built controls are considered uninitialized. Calling their methods
will cause the program to panic.NwgUi
, Native-windows-derive can automatically generate the Ui code. The derive macro has no way to actually know
how to actually create the controls, so more information must be provided over the fields.nwg_control
specifies the control parameters. The actual syntax is ident: expr
. The parameters names maps directly to
the control builder field (yes NWG uses builders to create controls, more on that later). Check a specific control documentation to see which
parameters are available.position: (300, 300)
. This is ok for this example because the window
cannot be resized, but NWG also supports layouts. nwg_events
specifies the events that the control responds to. It take an array of EVENT_NAME: [callbacks]
. Note that
this is only the default way to dispatch events. nwg_events can be further customized to match all your callback needs. Again, check
a specific control documentation to see which events can be raised.nwg::init()
. This function should not fail, but if it does, it's because the host system is really broken.
In such case, the only thing left to do is to panic. Note that some helper functions (such as nwg::simple_message
) can still be called.NativeUi
trait, which gives you the build_ui
function.
The build_ui
function takes a single argument: the initial state of your application (aka your UI struct default state).
Because this application is very simple, there's no user state to initialize, so we just pass Default::default
at
let native-windows-derive do its magic. nwg::dispatch_thread_events()
.
This function will block the thread until nwg::stop_thread_dispatch()
is called. NWG does not provide a default handler on window close,
so you will have to manually bind the `OnWindowClose` event/*!
A very simple application that show your name in a message box.
See `basic_d` for the derive version
*/
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 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()
.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();
}
build_ui
.nwg::CONTROL::builder()
method.
Default values and builder parameters are available in the documentation.build
function does not return the new control, instead you must pass a mutable reference to the function. This allows
further customization of controls using DerefMut
shenanigans.?
to propagate the error to the caller. parent
builder method."WINDOW|VISIBLE"
to nwg::WindowFlags::WINDOW | nwg::WindowFlags::VISIBLE
[Control]Ui
wraps the inner application data.full_bind_event_handler(parent_handle, callback)
full_bind_event_handler
do (with lots, lots of safety guards).
Event
, it's data (if any) EventData
, and the sender window handle ControlHandle
.WeakRef
is used here so that Drop
can be called.match evt
&handle == &evt_ui.hello_button
BasicApp::say_hello(&evt_ui.inner);
full_bind_event_handler
applies to the parent and all its children. This means you only need to bind handlers on TOP LEVEL window.
Lastly, the handler returned by full_bind_event_handler
is saved in the wrapper struct under default_handler
.
nwg::unbind_event_handler(&handler)
WeakRef
in our event handler, the ui struct only has 1 strong refcount. This way we can unbind the event handler in
the drop method.