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

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);
}
原因分析

问题出在两个核心点:

  1. 文本存储逻辑错误:使用单个字符串TEXT存储内容,每次添加新行时直接覆盖旧内容,之前的文本没有被保留。窗口未最小化时,之前绘制的内容还在客户区缓存中;但最小化后窗口客户区被销毁,还原时需要完全重绘,此时只能拿到最后一次存储的字符串。
  2. 绘制位置变量无意义:静态变量UPPER_LINE仅控制单次绘制的偏移,没有和所有文本行关联,完全重绘时无法回溯历史内容的绘制位置。
修复方案
  1. 将文本存储容器改为Vec<String>,每次添加新行时执行追加操作,保留所有历史文本。
  2. 在WM_PAINT消息中遍历所有文本行,根据行索引动态计算每行的绘制位置,逐行绘制。
  3. 移除冗余的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 22:31:56