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: 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!