winapi-rs调用InvalidateRect后WM_PAINT仅触发一次且绘制内容不显示
我尝试在全透明窗口上渲染部分图像区域,为了在事件回调中访问持久化数据,我将事件处理逻辑放在主循环而非WindowProc函数中,窗口实现代码如下:
#![windows_subsystem = "windows"] use std::ptr::{null_mut, null}; use std::ffi::{OsStr, c_void}; use std::iter::once; use std::os::windows::ffi::OsStrExt; use winapi::um::winuser::{SW_NORMAL, WM_PAINT, WM_TIMER, ShowWindow, SetLayeredWindowAttributes, GetMessageW, RegisterClassW, WNDCLASSW, CS_OWNDC, CS_HREDRAW, CS_VREDRAW, DefWindowProcW, WS_OVERLAPPED, WS_VISIBLE, CW_USEDEFAULT, MSG, TranslateMessage, DispatchMessageW, UpdateLayeredWindow, ULW_COLORKEY, GetDC, WS_EX_LAYERED, ReleaseDC, WS_EX_TOOLWINDOW, LWA_COLORKEY, WS_EX_TOPMOST, WS_POPUP, WS_SYSMENU, WS_THICKFRAME, WS_POPUPWINDOW, ChangeDisplaySettingsW, CDS_FULLSCREEN, SW_SHOWMAXIMIZED, CreateWindowExW, GetWindow, GW_HWNDNEXT, WS_EX_TRANSPARENT, GWL_EXSTYLE, WS_EX_DLGMODALFRAME, WS_EX_CLIENTEDGE, WS_EX_STATICEDGE, GetWindowLongW, SetWindowLongW, WS_EX_APPWINDOW, PAINTSTRUCT, BeginPaint, GetClientRect, EndPaint, LoadImageW, IMAGE_BITMAP, LR_LOADFROMFILE, InvalidateRect}; use std::ptr; use winapi::shared::minwindef::{HINSTANCE, LRESULT, UINT, LPARAM, WPARAM}; use winapi::shared::windef::{HWND__, POINT, SIZE, HDC__, HWND, RECT, HGDIOBJ, HDC}; use winapi::um::wingdi::{CreateCompatibleDC, RGB, BLENDFUNCTION, DeleteDC, AC_SRC_OVER, AC_SRC_ALPHA, DEVMODEW, SelectObject, GetStockObject, DC_PEN, CreatePen, PS_SOLID, Rectangle, DeleteObject, BitBlt, SRCCOPY}; use winapi::um::libloaderapi::GetModuleHandleW; use winapi::um::errhandlingapi::GetLastError; use winapi::um::winuser::{WM_KEYDOWN,WM_KEYUP}; use std::alloc::{alloc, Layout}; use std::collections::HashMap; fn win32_string( value : &str ) -> Vec<u16> { OsStr::new( value ).encode_wide().chain( once( 0 ) ).collect() } pub struct BasicWindow { window_class: WNDCLASSW, window_handle: *mut HWND__, mem_DC: Option<*mut HDC__>, screen_DC: Option<*mut HDC__>, } impl BasicWindow { pub unsafe extern "system" fn proc_msg(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT { match msg { _ => {return DefWindowProcW(hwnd, msg, wparam, lparam);} } } pub fn new(class_name: String, window_name: String) -> Self { unsafe { let hinstance = GetModuleHandleW(null_mut()); let wnd_class = WNDCLASSW { style: CS_HREDRAW | CS_OWNDC | CS_VREDRAW, lpfnWndProc: Some(Self::proc_msg), hInstance: hinstance as HINSTANCE, lpszClassName: win32_string(&class_name).as_ptr(), cbClsExtra: 0, cbWndExtra: 0, hIcon: null_mut(), hCursor: null_mut(), hbrBackground: null_mut(), lpszMenuName: null_mut(), }; let atom = RegisterClassW(&wnd_class); let handle: *mut HWND__ = CreateWindowExW(WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW, win32_string(&class_name).as_ptr(), win32_string(&window_name).as_ptr(), WS_POPUP | WS_VISIBLE, 0, 0, 1919, 1079, null_mut(), null_mut(), hinstance, null_mut() ) as *mut HWND__; ShowWindow(handle,SW_NORMAL); return Self { window_class: wnd_class, window_handle: handle, mem_DC: None, screen_DC: None, }; } } pub unsafe fn make_transparent(&mut self) { let show_outcome = ShowWindow(self.window_handle,SW_SHOWMAXIMIZED); let outcome = SetLayeredWindowAttributes(self.window_handle,RGB(0,0,0), 0, LWA_COLORKEY); println!("Error: {:?}\nSetLayeredWindowAttributes outcome: {:?}\nShowWindow outcome: {:?}", GetLastError(), outcome, show_outcome); } pub fn die(&mut self) { unsafe { match self.mem_DC { Some(mut hDC) => { DeleteDC(hDC); }, _ => {}, } match self.screen_DC { Some(mut hDC) => { ReleaseDC(self.window_handle,hDC); }, _ => {} } } } pub unsafe fn do_loop<T1: Fn() -> Option<(HDC, u32, u32, u32, u32)>, T2: Fn() -> Option<(u32,u32)>> (&mut self, tile_giver : T1, pos_giver : T2) { loop { let mut msg: MSG = std::mem::uninitialized(); if GetMessageW(&mut msg as *mut MSG, self.window_handle, 0, 0).is_positive() { match msg.message { WM_PAINT => { println!("WM_PAINT!!"); let mut ps = PAINTSTRUCT::default(); BeginPaint(self.window_handle,&mut ps); let mut rc = RECT::default(); GetClientRect(self.window_handle,&mut rc); let hdi_obj_original = SelectObject(ps.hdc, GetStockObject(DC_PEN as i32)); match (tile_giver(),pos_giver()) { (Some((hdc, x, y, w, h)),Some((xp,yp))) => { println!("blt returned: {:?}", BitBlt(ps.hdc, x as i32, y as i32, w as i32, h as i32, hdc, xp as i32, yp as i32, SRCCOPY)); }, _ => {} } SelectObject(ps.hdc, hdi_obj_original); EndPaint(self.window_handle, &ps); InvalidateRect(self.window_handle, &mut rc, 1); }, _ => {} } TranslateMessage(&msg as *const MSG); DispatchMessageW(&msg as *const MSG); } else { break; } } } }
后续我在main.rs中按如下方式使用该窗口结构体:
mod sprite_mgr; mod transparent_window; use transparent_window::BasicWindow; use sprite_mgr::{Sequence,SpriteMgr}; use winapi::um::libloaderapi::GetModuleHandleW; use std::ptr::null_mut; use winapi::shared::windef::HDC; fn main() { unsafe { let mut anim: Sequence = Sequence::new(); anim.chain_add((0, 0)).chain_add((1, 0)).chain_add((2, 0)); let mut sprite: SpriteMgr = SpriteMgr::new(GetModuleHandleW(null_mut()), "C:\\Users\\grass\\Desktop\\codes\\Rust\\catso\\src\\cat_spritesheet_largen.png\0", (2304, 1536), (9, 12)); sprite.add_seq("walk_fwd".to_owned(), anim); sprite.set_seq("walk_fwd".to_owned()); sprite.skip(); let thing = sprite.give_tile().unwrap(); let thing2 = sprite.give_pos().unwrap(); let mut win: BasicWindow = BasicWindow::new("hewwo".to_owned(), "UwU".to_owned()); win.make_transparent(); win.do_loop(|| -> Option<(HDC, u32, u32, u32, u32)> { let x = sprite.give_tile(); println!("{:?}",x); x }, || -> Option<(u32,u32)> { let x = sprite.give_pos(); println!("{:?}",x); x }); win.die(); } }
同时我自定义了Sequence和SpriteMgr两个结构体,定义如下:
Sequence定义
pub struct Sequence { body: Vec<(u32,u32)>, cursor: usize, } impl Sequence { pub fn new() -> Self { return Self { body: Vec::new(), cursor: 0 }; } pub fn chain_add(&mut self, arg: (u32,u32)) -> &mut Sequence { self.body.push(arg); self } pub fn next(&mut self) -> (u32,u32) { if self.cursor+ 1 as usize <= (&self.body).len() - 1 as usize { self.cursor += 1; } else { self.cursor = 0; } return (&self.body)[self.cursor]; } }
SpriteMgr定义
pub struct SpriteMgr { pub src: HDC, coordinates: (u32,u32), cell_dims: (u32,u32), cell_scale: (u32,u32), selected_cell: (u32,u32), sequences: HashMap<String,Sequence>, current_sequence: Option<String>, } impl SpriteMgr { pub fn new(hinstance: HINSTANCE, fname: &str, dims: (i32,i32), row_columns: (u32,u32)) -> Self { unsafe { let im_handle = LoadImageW(hinstance, win32_string(fname).as_ptr(), IMAGE_BITMAP, dims.0, dims.1, LR_LOADFROMFILE); let hdc: HDC = CreateCompatibleDC(null_mut()); SelectObject(hdc,im_handle); Self { src: hdc, coordinates: (0, 0), cell_dims: row_columns, cell_scale: ((dims.0 / row_columns.1 as i32) as u32, (dims.1 / row_columns.0 as i32) as u32), selected_cell: (0, 0), sequences: HashMap::new(), current_sequence: None, } } } pub fn add_seq(&mut self, k: String, v: Sequence) { &self.sequences.insert(k,v); } pub fn set_seq(&mut self, k: String) { if (&self.sequences).contains_key(&k) { self.current_sequence = Some(k); } } pub fn skip(&mut self) { match &self.current_sequence { Some(x) => { self.selected_cell = *(&self.sequences.get_mut(x).unwrap().next()); }, _ => {}, } } pub fn give_tile(&self) -> Option<(HDC,u32,u32,u32,u32)>{ match &self.current_sequence { Some(id) => { Some((self.src, self.selected_cell.0*self.cell_scale.0, self.selected_cell.1*self.cell_scale.1, self.cell_scale.0, self.cell_scale.1)) }, None => { None }, } } pub fn give_pos(&self) -> Option<(u32,u32)>{ Some(self.coordinates) } }
运行代码无报错也不崩溃,且BitBlt返回值为1代表执行成功,但屏幕上没有显示任何绘制内容。我尝试在WM_PAINT触发时打印日志,发现该消息仅在程序启动时触发一次,我猜测是透明窗口没有接收用户事件所以无需重绘,因此在WM_PAINT处理末尾调用InvalidateRect来触发重复重绘,但问题依旧存在。依赖方面我仅使用了带指定特性的winapi crate,特性列表为:["winapi/winuser","winapi/minwindef","winapi/windef", "winapi/wingdi","winapi/libloaderapi","winapi/errhandlingapi","winapi/impl-default"],请问该问题如何解决?
你遇到的问题是多个错误共同导致的,按优先级修复即可:
1. 修正BitBlt参数顺序错误(核心问题)
BitBlt的参数顺序你完全搞反了:
- 第2、3位是目标DC的绘制坐标,应该传你要把精灵放到窗口上的位置
- 第8、9位是源DC的取图坐标,应该传精灵在雪碧图里的偏移位置
你现在的代码把源坐标当成了目标坐标,要么画到了窗口可视区域外,要么取到的是雪碧图的空白区域,自然看不到内容。
把WM_PAINT里的BitBlt调用改成如下即可:
BitBlt( ps.hdc, xp as i32, // 目标x:精灵在窗口的显示位置 yp as i32, // 目标y:精灵在窗口的显示位置 w as i32, h as i32, hdc, x as i32, // 源x:精灵在雪碧图的偏移 y as i32, // 源y:精灵在雪碧图的偏移 SRCCOPY )
2. 修复WNDCLASSW的野指针问题
你在注册窗口类和创建窗口时,win32_string(&class_name).as_ptr()生成的是临时Vec<u16>的指针,调用完就被释放了,后续Windows访问类名时拿到的是野指针,窗口注册、创建都会异常。
修改BasicWindow结构体,把类名字符串存起来避免被释放:
pub struct BasicWindow { class_name: Vec<u16>, // 新增字段存储宽字节类名 window_class: WNDCLASSW, window_handle: *mut HWND__, mem_DC: Option<*mut HDC__>, screen_DC: Option<*mut HDC__>, }
new方法里先初始化class_name,再传入指针:
pub fn new(class_name: String, window_name: String) -> Self { unsafe { let class_name_wide = win32_string(&class_name); let hinstance = GetModuleHandleW(null_mut()); let wnd_class = WNDCLASSW { lpszClassName: class_name_wide.as_ptr(), // 其余字段保持不变 }; RegisterClassW(&wnd_class); let handle = CreateWindowExW( // 扩展样式不变 class_name_wide.as_ptr(), win32_string(&window_name).as_ptr(), // 其余参数不变 ); Self { class_name: class_name_wide, // 其余字段保持不变 } } }
3. 关闭背景擦除避免透明色冲突
你用LWA_COLORKEY指定纯黑色为透明色,InvalidateRect第三个参数传1会触发背景擦除,默认擦除会把区域填为黑色直接透明,修改两处:
- 在
WindowProc中添加WM_ERASEBKGND处理,直接返回1跳过擦除:pub unsafe extern "system" fn proc_msg(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT { match msg { WM_ERASEBKGND => return 1, _ => return DefWindowProcW(hwnd, msg, wparam, lparam), } } - 把
InvalidateRect的第三个参数改为0,不擦除背景:InvalidateRect(self.window_handle, &mut rc, 0);
4. 添加必要的错误检查
添加LoadImageW的返回值检查,确认图片路径

