Native Windows GUI: Events
NWG handles events by hooking the system events queue. This means that, unlike other GUI toolkit,
events are not really bound to a control. Instead, binding events is done
globally.
Because of the Rust borrow checker and the way events are handled internally by the windows API, any "traditional"
way to bind events ends up being too complicated. The two last version of NWG taught me that.
This sections explains how to bind events with NWG.
Note: to learn how to use raw events
HWND, WPARAM, LPARAM
, see
low level events handling.
Dispatching events
Before even thinking about hooking the event queue, the application must block the current thread and start listening to events.
This is easily done by calling
dispatch_thread_events
function.
In order to break the event loop, a call to
stop_thread_dispatch
must be done.
Closing all the active windows is not enough!
In a case like this, the event loop will beep running in the background until the process is killed.
The method
dispatch_thread_events
uses
GetMessage
under the hood and will block the thread.
Now, in some instances blocking the thread might not be wanted (ex:
opengl canvas).
In such cases, it's possible to pass a callback using the
dispatch_thread_events_with_callback
. Note that this function will not block the thread!
It's the equivalent of using a
loop
statement.
fn exit() {
stop_thread_dispatch();
}
// ...
dispatch_thread_events();
nwg::dispatch_thread_events_with_callback(|| {
});
Hooking the event queue
Hooking the system events queue can be done by calling one of those two functions:
full_bind_event_handler(control_handle, callback)
bind_event_handler(control_handle, parent_handle, callback)
The first one
"full" will hook the events of a control and ALL its children.
It's the one that will be used the most; At least once for every top level window.
The other one will hook the events of a control and its parent. Why the control and the parent?
Because the common controls of WINAPI sends their events to their parent. This method of hooking
is usually used when a callback is added dynamically. More on that
in the dynamic events section.
It is a VERY good idea to use a
WeakRef
to share data with an event handler to be sure that the shared value won't be leaked if
someone forgets to unbind the event handler.
let evt_ui = Rc::downgrade(&ui.inner);
let handle_events = move |evt, evt_data, handle| {
if let Some(events_ui) = evt_ui.upgrade() {
match evt {
nwg::Event::OnWindowClose => {
if &handle == &events_ui.window {
nwg::stop_thread_dispatch();
}
},
_ => {}
}
}
};
nwg::full_bind_event_handler(handle, handle_events);
Callback parameters
Th event handler parse the raw winapi parameters into 3 values:
Event
is an enum. Every event that can be raised by a NWG application is listed under it.
Some events, such as
MousePress(MousePressEvent)
also takes a sub-enum.
EventData
is also an enum. Most NWG events don't use data (
EventData::NoData
), this is because the raw data
that comes with winapi events are often limited to the size of 2
usize
. For example, instead of passing the width and height
on a
Event::OnResize
, it's better to call
control.size()
in the callback.
Handle
is the handle of the control. The events callback is not aware of the control in the application, as such
it can only send the handle. To fix this, NWG supports comparison of control vs handle
&handle == &events_ui.window
As such it's up to the developer to check which controller sent the event.
EventHandler
All event hooking funtions return a
EventHandler
object. This object is an opaque handle over the event hook and all the hooked controls.
Ignoring this value means that the callback and all its data will be leaked! In smaller appplication, it's perfectly fine to just ignore it,
but in large, long-running applications it's better to free the handler when it is no longer needed. This is done with:
nwg::unbind_event_handler(&handler)
Events handler are not freed automatically when they go out of scope!