I have a Windows application written in Rust and winapi that opens a window and writes text in the client area. The window procedure is as follows:
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_PAINT => {
println!( "WM_PAINT {}", text.len() );
let mut ps: PAINTSTRUCT = std::mem::zeroed();
let t: String = text.to_string();
let hdc: HDC;
hdc = BeginPaint(hwnd, &mut ps);
let mut rec: RECT = RECT {top: 0, bottom: 0, left: 0, right: 0};
GetClientRect(hwnd, &mut rec);
rec.top = 4;
rec.left = 6;
rec.bottom -= 4;
rec.right -= 6;
let txt = to_wstring( &t );
DrawTextW(hdc, txt.as_ptr(), txt.len().try_into().unwrap(), &mut rec, DT_TOP|DT_LEFT);
EndPaint(hwnd, &ps);
ReleaseDC(hwnd, hdc);
}
_ => return DefWindowProcW(hwnd, msg, wparam, lparam),
}
return 0;
}
The text in the client area is drawn when the window starts. Then I continuously change the text and send WM_PAINT
messages to the window procedure. The messages are received; I can see it in the println
output. However, the new text is not redrawn. The new text is drawn only when I resize the window; it is not drawn when I move it on the screen, even though it causes a train of WM_PAINT
messages.
I've also tried ExtTextOutW
and TextOutW
instead of DrawTextW
, but it doesn't change anything except that these two functions cannot handle multi-line texts.
Since the resizing solves the problem, I've also tried to modify the drawing rectangle rec
at each WM_PAINT
message, but it doesn't help.
What am I missing?
EDIT: Below I include the whole program, including the dependencies, to make reproducing the problem possible.
//#![cfg(windows)]
//#![windows_subsystem = "windows"]
//
//[dependencies]
//winapi = { version = "0.3.9", features = ["wingdi", "winuser", "libloaderapi", "combaseapi", "objbase", "shobjidl", "winerror"] }
//lazy_static = "1.4.0"
use std::thread;
use std::time;
use std::error::Error;
use std::ptr::null_mut;
use winapi::shared::minwindef::*;
use winapi::shared::windef::*;
use winapi::um::libloaderapi::{GetModuleHandleW};
use winapi::um::winuser::*;
use lazy_static::{lazy_static};
// Get a win32 lpstr from a &str, converting u8 to u16 and appending '\0'
fn to_wstring(value: &str) -> Vec<u16> {
use std::os::windows::ffi::OsStrExt;
std::ffi::OsStr::new(value).encode_wide()
.chain(std::iter::once(0)).collect()
}
// Window procedure function to handle events
//pub unsafe extern "system" fn window_proc(
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_PAINT => {
println!( "WM_PAINT {}", TEXT.len() );
let mut ps: PAINTSTRUCT = std::mem::zeroed();
let t: String = TEXT.to_string();
let hdc: HDC;
hdc = BeginPaint(hwnd, &mut ps);
let mut rec: RECT = RECT {top: 0, bottom: 0, left: 0, right: 0};
GetClientRect(hwnd, &mut rec);
rec.top = 4;
rec.left = 6;
rec.bottom -= 4;
rec.right -= 6;
let txt = to_wstring( &t );
DrawTextW(hdc, txt.as_ptr(), txt.len().try_into().unwrap(), &mut rec, DT_TOP|DT_LEFT);
EndPaint(hwnd, &ps);
ReleaseDC(hwnd, hdc);
}
_ => 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 = to_wstring(name);
let title = to_wstring(title);
unsafe {
// Get handle to the file used to create the calling process
let hinstance = GetModuleHandleW(null_mut());
// Create and register window class
let wnd_class = WNDCLASSEXW {
cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(window_proc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: hinstance, // Handle to the instance that contains the window procedure for the class
hIcon: LoadIconW(null_mut(), IDI_APPLICATION),
hCursor: LoadCursorW(null_mut(), IDC_ARROW),
hbrBackground: COLOR_WINDOWFRAME as HBRUSH,
lpszMenuName: null_mut(),
lpszClassName: name.as_ptr(),
hIconSm: LoadIconW(null_mut(), IDI_APPLICATION),
};
// Register window class
if RegisterClassExW(&wnd_class) == 0 {
MessageBoxW(
null_mut(),
to_wstring("Window Registration Failed!").as_ptr(),
to_wstring("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
710, // Int x
580, // Int y
700, // Int nWidth
400, // Int nHeight
null_mut(), // hWndParent
null_mut(), // hMenu
hinstance, // hInstance
null_mut(), // lpParam
);
if handle.is_null() {
MessageBoxW(
null_mut(),
to_wstring("Window Creation Failed!").as_ptr(),
to_wstring("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() {
unsafe {
TEXT = T_CONST.clone();
}
let hwnd = create_main_window("Main Window", "Main Window")
.expect("Window creation failed!");
unsafe {
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
}
thread::spawn( || {
loop {
thread::sleep(time::Duration::from_millis(2000));
unsafe {
let t = TEXT.clone();
TEXT = t &T_CONST.clone();
PostMessageW (HWND_BROADCAST as HWND, WM_PAINT, 0 as WPARAM, 0 as LPARAM);
}
}
});
run_message_loop(hwnd);
}
static mut TEXT: String = String::new();
lazy_static! {
static ref T_CONST: String = "This is the first line of the text
and this is the third line
and now comes the last line
".to_string();
}
CodePudding user response:
Problems I found:
- Don't call
ReleaseDC
afterEndPaint
, that's already part ofEndPaint
. - Only regions that weren't rendered yet get updated. You can see that if you drag the window out of the screen and back in, then the regions that were previously off-screen get updated. You can manually invalidate regions via calling
InvalidateRect
orRedrawWindow
. In your case, you probably want to callRedrawWindow
instead of sending aWM_PAINT
message.
use lazy_static::lazy_static;
use std::error::Error;
use std::ptr::{null, null_mut};
use std::thread;
use std::time;
use winapi::shared::minwindef::*;
use winapi::shared::windef::*;
use winapi::um::libloaderapi::GetModuleHandleW;
use winapi::um::winuser::*;
// Get a win32 lpstr from a &str, converting u8 to u16 and appending '\0'
fn to_wstring(value: &str) -> Vec<u16> {
use std::os::windows::ffi::OsStrExt;
std::ffi::OsStr::new(value)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
// Window procedure function to handle events
//pub unsafe extern "system" fn window_proc(
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_PAINT => {
println!("WM_PAINT {}", TEXT.len());
let mut ps: PAINTSTRUCT = std::mem::zeroed();
let t: String = TEXT.to_string();
let hdc: HDC;
hdc = BeginPaint(hwnd, &mut ps);
let mut rec: RECT = RECT {
top: 0,
bottom: 0,
left: 0,
right: 0,
};
GetClientRect(hwnd, &mut rec);
rec.top = 4;
rec.left = 6;
rec.bottom -= 4;
rec.right -= 6;
let txt = to_wstring(&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 = to_wstring(name);
let title = to_wstring(title);
unsafe {
// Get handle to the file used to create the calling process
let hinstance = GetModuleHandleW(null_mut());
// Create and register window class
let wnd_class = WNDCLASSEXW {
cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(window_proc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: hinstance, // Handle to the instance that contains the window procedure for the class
hIcon: LoadIconW(null_mut(), IDI_APPLICATION),
hCursor: LoadCursorW(null_mut(), IDC_ARROW),
hbrBackground: COLOR_WINDOWFRAME as HBRUSH,
lpszMenuName: null_mut(),
lpszClassName: name.as_ptr(),
hIconSm: LoadIconW(null_mut(), IDI_APPLICATION),
};
// Register window class
if RegisterClassExW(&wnd_class) == 0 {
MessageBoxW(
null_mut(),
to_wstring("Window Registration Failed!").as_ptr(),
to_wstring("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
710, // Int x
580, // Int y
700, // Int nWidth
400, // Int nHeight
null_mut(), // hWndParent
null_mut(), // hMenu
hinstance, // hInstance
null_mut(), // lpParam
);
if handle.is_null() {
MessageBoxW(
null_mut(),
to_wstring("Window Creation Failed!").as_ptr(),
to_wstring("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() {
unsafe {
TEXT = T_CONST.clone();
}
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 || loop {
thread::sleep(time::Duration::from_millis(2000));
unsafe {
let t = TEXT.clone();
TEXT = t &T_CONST.clone();
RedrawWindow(
hwnd2 as HWND,
null(),
null_mut(),
RDW_INVALIDATE | RDW_ERASE,
);
}
});
run_message_loop(hwnd);
}
static mut TEXT: String = String::new();
lazy_static! {
static ref T_CONST: String = "This is the first line of the text
and this is the third line
and now comes the last line
"
.to_string();
}
There are a couple more things I would like to remark:
- Global
static mut String
is inherentlyunsafe
and incompatible with multiple threads. In fact, the way you implemented it here is undefined behaviour, because theWM_PAINT
handler could run at the same time as the string gets modified. Usestatic Mutex<String>
instead. lazy_static! { static ref T_CONST: String }
is quite pointless; there is a mechanism specifically meant for const strings, it's calledconst &str
.&T_CONST.clone()
is pointless,&T_CONST
(orT_CONST.as_str()
if it's wrapped in alazy_static
) does exactly the same; or after refactoring it to aconst &str
, simplyT_CONST
.HWND_BROADCAST as HWND
is pointless,HWND_BROADCAST
is alreadyHWND
.- Most of the WinAPI functions return
BOOL
for error handling, which you completely ignore. I won't fix that, though, because it's more work than I'm willed to put into this :)
With most of those fixed, this is how the code would look like:
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::*;
// Get a win32 lpstr from a &str, converting u8 to u16 and appending '\0'
fn to_wstring(value: &str) -> Vec<u16> {
use std::os::windows::ffi::OsStrExt;
std::ffi::OsStr::new(value)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
// Window procedure function to handle events
//pub unsafe extern "system" fn window_proc(
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_PAINT => {
let t = TEXT.lock().unwrap().clone();
println!("WM_PAINT {}", t.len());
let mut ps: PAINTSTRUCT = std::mem::zeroed();
let hdc: HDC;
hdc = BeginPaint(hwnd, &mut ps);
let mut rec: RECT = RECT {
top: 0,
bottom: 0,
left: 0,
right: 0,
};
GetClientRect(hwnd, &mut rec);
rec.top = 4;
rec.left = 6;
rec.bottom -= 4;
rec.right -= 6;
let txt = to_wstring(&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 = to_wstring(name);
let title = to_wstring(title);
unsafe {
// Get handle to the file used to create the calling process
let hinstance = GetModuleHandleW(null_mut());
// Create and register window class
let wnd_class = WNDCLASSEXW {
cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(window_proc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: hinstance, // Handle to the instance that contains the window procedure for the class
hIcon: LoadIconW(null_mut(), IDI_APPLICATION),
hCursor: LoadCursorW(null_mut(), IDC_ARROW),
hbrBackground: COLOR_WINDOWFRAME as HBRUSH,
lpszMenuName: null_mut(),
lpszClassName: name.as_ptr(),
hIconSm: LoadIconW(null_mut(), IDI_APPLICATION),
};
// Register window class
if RegisterClassExW(&wnd_class) == 0 {
MessageBoxW(
null_mut(),
to_wstring("Window Registration Failed!").as_ptr(),
to_wstring("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
710, // Int x
580, // Int y
700, // Int nWidth
400, // Int nHeight
null_mut(), // hWndParent
null_mut(), // hMenu
hinstance, // hInstance
null_mut(), // lpParam
);
if handle.is_null() {
MessageBoxW(
null_mut(),
to_wstring("Window Creation Failed!").as_ptr(),
to_wstring("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() = T_CONST.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 || loop {
thread::sleep(time::Duration::from_millis(2000));
*TEXT.lock().unwrap() = T_CONST;
unsafe {
RedrawWindow(
hwnd2 as HWND,
null(),
null_mut(),
RDW_INVALIDATE | RDW_ERASE,
);
}
});
run_message_loop(hwnd);
}
static TEXT: Mutex<String> = Mutex::new(String::new());
const T_CONST: &str = "This is the first line of the text
and this is the third line
and now comes the last line
";
Alternative: Don't use a thread
The best practice for working with UI is to not use threads; or if you do, only use UI-related calls from the main thread.
Here is a version with a timer that achieves the same thing:
use std::error::Error;
use std::ptr::{null, null_mut};
use std::sync::Mutex;
use winapi::shared::minwindef::*;
use winapi::shared::windef::*;
use winapi::um::libloaderapi::GetModuleHandleW;
use winapi::um::winuser::*;
// Get a win32 lpstr from a &str, converting u8 to u16 and appending '\0'
fn to_wstring(value: &str) -> Vec<u16> {
use std::os::windows::ffi::OsStrExt;
std::ffi::OsStr::new(value)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
const IDT_TIMER1: usize = 1;
// Window procedure function to handle events
//pub unsafe extern "system" fn window_proc(
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_TIMER => match wparam {
IDT_TIMER1 => {
*TEXT.lock().unwrap() = T_CONST;
RedrawWindow(hwnd, null(), null_mut(), RDW_INVALIDATE | RDW_ERASE);
}
_ => (),
},
WM_PAINT => {
let t = TEXT.lock().unwrap().clone();
println!("WM_PAINT {}", t.len());
let mut ps: PAINTSTRUCT = std::mem::zeroed();
let hdc: HDC;
hdc = BeginPaint(hwnd, &mut ps);
let mut rec: RECT = RECT {
top: 0,
bottom: 0,
left: 0,
right: 0,
};
GetClientRect(hwnd, &mut rec);
rec.top = 4;
rec.left = 6;
rec.bottom -= 4;
rec.right -= 6;
let txt = to_wstring(&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 = to_wstring(name);
let title = to_wstring(title);
unsafe {
// Get handle to the file used to create the calling process
let hinstance = GetModuleHandleW(null_mut());
// Create and register window class
let wnd_class = WNDCLASSEXW {
cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(window_proc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: hinstance, // Handle to the instance that contains the window procedure for the class
hIcon: LoadIconW(null_mut(), IDI_APPLICATION),
hCursor: LoadCursorW(null_mut(), IDC_ARROW),
hbrBackground: COLOR_WINDOWFRAME as HBRUSH,
lpszMenuName: null_mut(),
lpszClassName: name.as_ptr(),
hIconSm: LoadIconW(null_mut(), IDI_APPLICATION),
};
// Register window class
if RegisterClassExW(&wnd_class) == 0 {
MessageBoxW(
null_mut(),
to_wstring("Window Registration Failed!").as_ptr(),
to_wstring("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
710, // Int x
580, // Int y
700, // Int nWidth
400, // Int nHeight
null_mut(), // hWndParent
null_mut(), // hMenu
hinstance, // hInstance
null_mut(), // lpParam
);
if handle.is_null() {
MessageBoxW(
null_mut(),
to_wstring("Window Creation Failed!").as_ptr(),
to_wstring("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() = T_CONST.to_string();
let hwnd = create_main_window("Main Window", "Main Window").expect("Window creation failed!");
unsafe {
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
SetTimer(hwnd, IDT_TIMER1, 2000, None);
}
run_message_loop(hwnd);
}
static TEXT: Mutex<String> = Mutex::new(String::new());
const T_CONST: &str = "This is the first line of the text
and this is the third line
and now comes the last line
";