Rust+winapi窗口最小化后重绘丢失历史文本问题排查
问题
使用Rust结合winapi开发的桌面应用,打开窗口后通过多次调用RedrawWindow()添加文本行,文本行能正常显示,调整窗口大小、移动或遮挡时也能保留,但窗口最小化后再次还原时,仅显示最后一行文本,之前的行全部消失。
初始状态:
还原后状态:
原始代码:
//[dependencies] //winapi = { version = "0.3.9", features = ["wingdi", "winuser", "libloaderapi", "combaseapi", "objbase", "shobjidl", "winerror"] } use std::error::Error; use std::ptr::{null, null_mut}; use std::sync::Mutex; use std::thread; use std::time; use winapi::shared::minwindef::*; use winapi::shared::windef::*; use winapi::um::libloaderapi::GetModuleHandleW; use winapi::um::winuser::*; // Custom signal to inform that new text is available pub const WM_WEBUPDT: UINT = 0xFEDC; /// Turns a Rust string slice into a null-terminated utf-16 vector. pub fn wide_null(s: &str) -> Vec<u16> { s.encode_utf16().chain(Some(0)).collect() } static TEXT: Mutex<String> = Mutex::new(String::new()); static mut UPPER_LINE: i32 = 0; // Window procedure to handle events pub unsafe extern "system" fn window_proc( hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM, ) -> LRESULT { match msg { WM_CLOSE => { DestroyWindow(hwnd); } WM_DESTROY => { PostQuitMessage(0); } WM_WEBUPDT => { RedrawWindow(hwnd, null(), null_mut(), RDW_INVALIDATE); UPPER_LINE += 25; } WM_PAINT => { let t: String = TEXT.lock().unwrap().clone(); let mut ps: PAINTSTRUCT = std::mem::zeroed(); let hdc: HDC; hdc = BeginPaint(hwnd, &mut ps); let mut rec: RECT = std::mem::zeroed(); GetClientRect(hwnd, &mut rec); rec.top += 4 + UPPER_LINE; let txt = wide_null(&t); DrawTextW( hdc, txt.as_ptr(), txt.len().try_into().unwrap(), &mut rec, DT_TOP | DT_LEFT, ); EndPaint(hwnd, &ps); } _ => return DefWindowProcW(hwnd, msg, wparam, lparam), } return 0; } // Declare class and instantiate window fn create_main_window(name: &str, title: &str) -> Result<HWND, Box<dyn Error>> { let name = wide_null(name); let title = wide_null(title); unsafe { let hinstance = GetModuleHandleW(null_mut()); let mut wc: WNDCLASSW = core::mem::zeroed(); wc.lpfnWndProc = Some(window_proc); wc.hInstance = hinstance; wc.hbrBackground = COLOR_WINDOWFRAME as HBRUSH; wc.lpszClassName = name.as_ptr(); // Register window class if RegisterClassW(&wc) == 0 { MessageBoxW( null_mut(), wide_null("Window Registration Failed!").as_ptr(), wide_null("Error").as_ptr(), MB_ICONEXCLAMATION | MB_OK, ); return Err("Window Registration Failed".into()); }; // Create a window based on registered class let handle = CreateWindowExW( 0, // dwExStyle name.as_ptr(), // lpClassName title.as_ptr(), // lpWindowName WS_OVERLAPPEDWINDOW | WS_VISIBLE, // dwStyle 810, // Int x 390, // Int y 300, // Int nWidth 300, // Int nHeight null_mut(), // hWndParent null_mut(), // hMenu hinstance, // hInstance null_mut(), // lpParam ); if handle.is_null() { MessageBoxW( null_mut(), wide_null("Window Creation Failed!").as_ptr(), wide_null("Error!").as_ptr(), MB_ICONEXCLAMATION | MB_OK, ); return Err("Window Creation Failed!".into()); } Ok(handle) } } // Message handling loop fn run_message_loop(hwnd: HWND) -> WPARAM { unsafe { let mut msg: MSG = std::mem::zeroed(); loop { // Get message from message queue if GetMessageW(&mut msg, hwnd, 0, 0) > 0 { TranslateMessage(&msg); DispatchMessageW(&msg); } else { // Return on error (<0) or exit (=0) cases return msg.wParam; } } } } fn main() { *TEXT.lock().unwrap() = "This is first line".to_string(); let hwnd = create_main_window("Main Window", "Main Window").expect("Window creation failed!"); unsafe { ShowWindow(hwnd, SW_SHOW); UpdateWindow(hwnd); } let hwnd2 = hwnd as usize; thread::spawn(move || { unsafe { thread::sleep(time::Duration::from_millis(100)); *TEXT.lock().unwrap() = "This is second line".to_string(); PostMessageW (hwnd2 as HWND, WM_WEBUPDT, 0 as WPARAM, 0 as LPARAM); thread::sleep(time::Duration::from_millis(100)); *TEXT.lock().unwrap() = "This is third line".to_string(); PostMessageW (hwnd2 as HWND, WM_WEBUPDT, 0 as WPARAM, 0 as LPARAM); } } ); run_message_loop(hwnd); }
原因分析
问题出在两个核心点:
- 文本存储逻辑错误:使用单个字符串
TEXT存储内容,每次添加新行时直接覆盖旧内容,之前的文本没有被保留。窗口未最小化时,之前绘制的内容还在客户区缓存中;但最小化后窗口客户区被销毁,还原时需要完全重绘,此时只能拿到最后一次存储的字符串。 - 绘制位置变量无意义:静态变量
UPPER_LINE仅控制单次绘制的偏移,没有和所有文本行关联,完全重绘时无法回溯历史内容的绘制位置。
修复方案
- 将文本存储容器改为
Vec<String>,每次添加新行时执行追加操作,保留所有历史文本。 - 在
WM_PAINT消息中遍历所有文本行,根据行索引动态计算每行的绘制位置,逐行绘制。 - 移除冗余的
UPPER_LINE静态变量。
修改后的代码:
//[dependencies] //winapi = { version = "0.3.9", features = ["wingdi", "winuser", "libloaderapi", "combaseapi", "objbase", "shobjidl", "winerror"] } use std::error::Error; use std::ptr::{null, null_mut}; use std::sync::Mutex; use std::thread; use std::time; use winapi::shared::minwindef::*; use winapi::shared::windef::*; use winapi::um::libloaderapi::GetModuleHandleW; use winapi::um::winuser::*; // Custom signal to inform that new text is available pub const WM_WEBUPDT: UINT = 0xFEDC; // 每行文本的垂直间距 const LINE_SPACING: i32 = 25; /// Turns a Rust string slice into a null-terminated utf-16 vector. pub fn wide_null(s: &str) -> Vec<u16> { s.encode_utf16().chain(Some(0)).collect() } // 存储所有文本行,而非单个字符串 static TEXT_LINES: Mutex<Vec<String>> = Mutex::new(Vec::new()); // Window procedure to handle events pub unsafe extern "system" fn window_proc( hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM, ) -> LRESULT { match msg { WM_CLOSE => { DestroyWindow(hwnd); } WM_DESTROY => { PostQuitMessage(0); } WM_WEBUPDT => { RedrawWindow(hwnd, null(), null_mut(), RDW_INVALIDATE); } WM_PAINT => { let lines = TEXT_LINES.lock().unwrap().clone(); let mut ps: PAINTSTRUCT = std::mem::zeroed(); let hdc = BeginPaint(hwnd, &mut ps); let mut rec: RECT = std::mem::zeroed(); GetClientRect(hwnd, &mut rec); // 逐行绘制所有历史文本 for (index, line) in lines.iter().enumerate() { let mut line_rect = rec; // 计算当前行的垂直位置:顶部边距 + 行索引*行间距 line_rect.top += 4 + (index as i32) * LINE_SPACING; let txt = wide_null(line); DrawTextW( hdc, txt.as_ptr(), txt.len().try_into().unwrap(), &mut line_rect, DT_TOP | DT_LEFT, ); } EndPaint(hwnd, &ps); } _ => return DefWindowProcW(hwnd, msg, wparam, lparam), } return 0; } // Declare class and instantiate window fn create_main_window(name: &str, title: &str) -> Result<HWND, Box<dyn Error>> { let name = wide_null(name); let title = wide_null(title); unsafe { let hinstance = GetModuleHandleW(null_mut()); let mut wc: WNDCLASSW = core::mem::zeroed(); wc.lpfnWndProc = Some(window_proc); wc.hInstance = hinstance; wc.hbrBackground = COLOR_WINDOWFRAME as HBRUSH; wc.lpszClassName = name.as_ptr(); // Register window class if RegisterClassW(&wc) == 0 { MessageBoxW( null_mut(), wide_null("窗口注册失败!").as_ptr(), wide_null("错误").as_ptr(), MB_ICONEXCLAMATION | MB_OK, ); return Err("窗口注册失败".into()); }; // Create a window based on registered class let handle = CreateWindowExW( 0, // dwExStyle name.as_ptr(), // lpClassName title.as_ptr(), // lpWindowName WS_OVERLAPPEDWINDOW | WS_VISIBLE, // dwStyle 810, // Int x 390, // Int y 300, // Int nWidth 300, // Int nHeight null_mut(), // hWndParent null_mut(), // hMenu hinstance, // hInstance null_mut(), // lpParam ); if handle.is_null() { MessageBoxW( null_mut(), wide_null("窗口创建失败!").as_ptr(), wide_null("错误!").as_ptr(), MB_ICONEXCLAMATION | MB_OK, ); return Err("窗口创建失败!".into()); } Ok(handle) } } // Message handling loop fn run_message_loop(hwnd: HWND) -> WPARAM { unsafe { let mut msg: MSG = std::mem::zeroed(); loop { // Get message from message queue if GetMessageW(&mut msg, hwnd, 0, 0) > 0 { TranslateMessage(&msg); DispatchMessageW(&msg); } else { // Return on error (<0) or exit (=0) cases return msg.wParam; } } } } fn main() { TEXT_LINES.lock().unwrap().push("这是第一行".to_string()); let hwnd = create_main_window("主窗口", "主窗口").expect("窗口创建失败!"); unsafe { ShowWindow(hwnd, SW_SHOW); UpdateWindow(hwnd); } let hwnd2 = hwnd as usize; thread::spawn(move || { unsafe { thread::sleep(time::Duration::from_millis(100)); TEXT_LINES.lock().unwrap().push("这是第二行".to_string()); PostMessageW (hwnd2 as HWND, WM_WEBUPDT, 0 as WPARAM, 0 as LPARAM); thread::sleep(time::Duration::from_millis(100)); TEXT_LINES.lock().unwrap().push("这是第三行".to_string()); PostMessageW (hwnd2 as HWND, WM_WEBUPDT, 0 as WPARAM, 0 as LPARAM); } }); run_message_loop(hwnd); }
内容的提问来源于stack exchange,提问作者Kalle Svensson
相关产品推荐
相关产品推荐

