rodrigocfd / winsafe

Windows API and GUI in safe, idiomatic Rust.
https://crates.io/crates/winsafe
MIT License
520 stars 30 forks source link

How do you envision WM_USER being used? #109

Closed caesay closed 11 months ago

caesay commented 12 months ago

It's very common practice in C++ / win32 to define custom window messages (eg. WM_USER + 1) as constants and handle those in your message loop. I am fairly new to rust, so I might be missing something, but it doesn't seem to currently be possible. This provides an easy way to communicate between windows/threads when you already have a message loop.

For example, define your constants:

pub const MSG_SOME1: u32 = co::WM::USER.raw() + 1;
pub const MSG_THING2: u32 = co::WM::USER.raw() + 2;
pub const MSG_HERE3: u32 = co::WM::USER.raw() + 3;

Then try to hook up to these (following the custom controls example):

fn events(&mut self) {
    let self2 = self.clone();
    self.wnd.on().wm(MSG_SOME1 as co::WM, move |v| {

        Ok(None)
    });
}

It's doesn't seem possible to do this, because as far as I can tell integers in rust can't be cast to enums like they can in other languages like C++ or C#. Shouldn't there be a version of .wm() which accepts an integer instead of the WM enum?

This argument would extend to PostMessage / SendMessage, the compliment to this approach.

rodrigocfd commented 11 months ago

Defining new WM constant can be done just like any other constant:

pub const MAKE_TOAST: co::WM = unsafe { co::WM::from_raw(co::WM::USER.raw() + 20) };

However, this is just the tip of the iceberg.

One of the greatest challenges while writing WinSafe's GUI module was how to deal with type-safe messages, since they have to be:

So, in order to properly declare a custom message, you must fulfill the bullets above. As an example, imagine that MAKE_TOAST message, whose data is simply how many toasts you should make. It could be implemented like this:

use winsafe::{self as w, prelude::*, co, msg};

pub const MAKE_TOAST: co::WM = unsafe { co::WM::from_raw(co::WM::USER.raw() + 20) };

struct MakeToast {
    how_many: u32,
}

unsafe impl MsgSend for MakeToast {
    type RetType = ();

    fn convert_ret(&self, _: isize) -> Self::RetType {
        ()
    }

    fn as_generic_wm(&mut self) -> msg::WndMsg {
        msg::WndMsg {
            msg_id: MAKE_TOAST,
            wparam: self.how_many as _,
            lparam: 0,
        }
    }
}

unsafe impl MsgSendRecv for MakeToast {
    fn from_generic_wm(p: msg::WndMsg) -> Self {
        Self {
            how_many: p.wparam as _,
        }
    }
}

First, you define the struct with the data it contains, then you implement MsgSend, so you message can be sent through SendMessage. And finally you implement MsgSendRecv, so your message can also be received in an on().wm(...) handler.

Now sending becomes trivial:

self.wnd.hwnd().SendMessage(
    MakeToast {
        how_many: 3,
    },
);

And when receiving, you must manually cast the generic parameters to the specialized struct:

self.wnd.on().wm(MAKE_TOAST, move |p| {
    let p = MakeToast::from_generic_wm(p);
    println!("Make {}", p.how_many);
    Ok(None)
});

In fact, you made me realize that there is no clear documentation like this in the docs. This example is now here.