You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

winapi-rs调用InvalidateRect后WM_PAINT仅触发一次且绘制内容不显示

问题:Win10下Rust winapi分层透明窗口BitBlt成功但无绘制内容

我尝试在全透明窗口上渲染部分图像区域,为了在事件回调中访问持久化数据,我将事件处理逻辑放在主循环而非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的返回值检查,确认图片路径

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.18 02:45:03