Open Marko19907 opened 1 year ago
I wrote a helper function to update a menu item's text using the windows
crate:
use nwg::ControlHandle;
/// Copied from [`native_windows_gui::win32::base_helper::to_utf16`].
fn to_utf16(s: &str) -> Vec<u16> {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
OsStr::new(s)
.encode_wide()
.chain(core::iter::once(0u16))
.collect()
}
/**
Return the index of a children menu/menuitem in a parent menu.
Panic if the menu is not found in the parent.
Adapted from [`native_windows_gui::win32::menu::menu_index_in_parent`] (in the private `win32` module).
*/
pub fn menu_index_in_parent(menu_handle: ControlHandle) -> Option<u32> {
if menu_handle.blank() {
return None;
}
let (parent, menu) = menu_handle.hmenu()?;
// Safety: we check the same preconditions as the nwg crate does when it
// calls this function on a menu.
use windows::Win32::UI::WindowsAndMessaging::{GetMenuItemCount, GetSubMenu, HMENU};
let parent = HMENU(parent as isize);
let children_count = unsafe { GetMenuItemCount(parent) };
let mut sub_menu;
for i in 0..children_count {
sub_menu = unsafe { GetSubMenu(parent, i) };
if sub_menu.0 == 0 {
continue;
} else if sub_menu.0 == (menu as isize) {
return Some(i as u32);
}
}
None
}
/// Update the text of a submenu or menu item.
pub fn set_menu_item_text(handle: ControlHandle, text: &str) {
if handle.blank() {
panic!("Unbound handle");
}
enum MenuItemInfo {
Position(u32),
Id(u32),
}
let (parent, item_info) = match handle {
ControlHandle::Menu(parent, _) => {
// Safety: the handles inside ControlHandle is valid, according to
// https://gabdube.github.io/native-windows-gui/native-windows-docs/extern_wrapping.html
// constructing new ControlHandle instances should be considered
// unsafe.
if let Some(index) = menu_index_in_parent(handle) {
(parent, MenuItemInfo::Position(index))
} else {
return;
}
}
ControlHandle::MenuItem(parent, id) => (parent, MenuItemInfo::Id(id)),
_ => return,
};
use windows::{
core::PWSTR,
Win32::UI::WindowsAndMessaging::{SetMenuItemInfoW, HMENU, MENUITEMINFOW, MIIM_STRING},
};
// The code below was inspired by `nwg::win32::menu::enable_menuitem`
// and: https://stackoverflow.com/questions/25139819/change-text-of-an-menu-item
let use_position = matches!(item_info, MenuItemInfo::Position(_));
let value = match item_info {
MenuItemInfo::Position(p) => p,
MenuItemInfo::Id(id) => id,
};
let text = to_utf16(text);
let mut info = MENUITEMINFOW::default();
info.cbSize = core::mem::size_of_val(&info) as u32;
info.fMask = MIIM_STRING;
info.dwTypeData = PWSTR(text.as_ptr().cast_mut());
let _ = unsafe { SetMenuItemInfoW(HMENU(parent as _), value, use_position, &info) };
}
/// Remove a submenu from its parent. Note that this is not done automatically
/// when a menu is dropped.
pub fn remove_menu(menu: &nwg::Menu) {
if menu.handle.blank() {
return;
}
let Some((parent, _)) = menu.handle.hmenu() else {
return;
};
let Some(index) = menu_index_in_parent(menu.handle) else {
return;
};
use windows::Win32::UI::WindowsAndMessaging::{RemoveMenu, HMENU, MF_BYPOSITION};
let _ = unsafe { RemoveMenu(HMENU(parent as isize), index, MF_BYPOSITION) };
}
Hi, I'm new to using the native-windows-gui (NWG) Rust library and I'm also new to Rust in general. I apologize if this is a basic question. I'm currently working on a system tray app and I was wondering if there is a way to update the text of a MenuItem at runtime?