我正在尝试自动安装 Inno Setup,我已经能够与按钮和复选框进行通信,但我一直无法与
TNewCheckListBox
进行通信。如果我正确理解了文档,每个 TNewCheckBox
都是动态创建的,但我根本无法与它们交流,甚至无法看到它们。
我找到了一种获取其文本的方法,但是我无法单击它们或进行设置检查。 我尝试了
CLBN_CHKCHANGE
的实现,但没有成功。
有部分代码:
pub fn check_checklistbox_states() -> Vec<HWND> {
let mut checkbox_handles: Vec<HWND> = Vec::new();
if let Some(checklistbox_hwnd) = find_checklistbox() {
unsafe {
let count = SendMessageW(checklistbox_hwnd, LB_GETCOUNT, WPARAM(0), LPARAM(0));
let count = count.0 as i32 as usize;
println!("{:#?}", count);
for index in 0..count {
if let Some(text) = get_item_text(checklistbox_hwnd, index) {
let state = SendMessageW(checklistbox_hwnd, LB_GETITEMDATA, WPARAM(index as usize), LPARAM(0));
let checked = state.0 as i32;
let state_text = if checked != 0 {
"Checked"
} else {
"Unchecked"
};
let debug_checkbox = checklistbox_hwnd.0 as i32;
println!("Item {}: {} - {} - {}", index + 1, text, state_text, debug_checkbox);
// Example to toggle the check state
let new_check_state = if checked != 0 { 0 } else { 1 }; // Toggle between checked (1) and unchecked (0)
if (index >= 1) {
// set_check(checklistbox_hwnd, index as c_int, new_check_state, BS_CHECKBOX as u32);
let _ = PostMessageW(checklistbox_hwnd, CLBN_CHKCHANGE, WPARAM(1), LPARAM(0));
println!("sent");
}
println!("should have clicked");
checkbox_handles.push(checklistbox_hwnd); // This is the handle of the list box, not the item
}
}
}
} else {
println!("TNewCheckListBox not found.");
}
checkbox_handles
}
在这里,我尝试获取复选框句柄,但它不起作用,我之前通过此常量定义了
CLBN_CHKCHANGE
:
const CLBN_CHKCHANGE: u32 = 40;
我也尝试过这个const CLBN_CHKCHANGE: u32 = 0x00000040;
我尝试了更复杂的set_check用法,类似于winscp源代码中的
CCheckListBox::SetCheck(int nIndex, int nCheck)
。
就是这样,如果代码不好,我深表歉意,因为我对 Rust 还很陌生,并且一些包含 Windows API 的 C++ 代码的转换很困难而且不完整。
我在这里定义了结构:
const LB_ERR: LRESULT = windows::Win32::Foundation::LRESULT(-1);
#[repr(C)]
struct AfxCheckData {
m_n_check: c_int,
m_b_enabled: BOOL,
m_dw_user_data: DWORD,
}
impl AfxCheckData {
fn new() -> Self {
Self {
m_n_check: 0,
m_b_enabled: TRUE,
m_dw_user_data: 0,
}
}
}
然后,如果我理解正确的话,我尝试像
CCheckListBox::InvalidateCheck(int nIndex)
那样进行 invalidate_check 来处理错误。
fn invalidate_check(listbox_hwnd: HWND, n_index: i32) {
unsafe {
let mut rect: RECT = zeroed();
if SendMessageW(listbox_hwnd, LB_GETITEMRECT, WPARAM(n_index as usize), LPARAM(&mut rect as *mut RECT as isize)) != LB_ERR {
InvalidateRect(listbox_hwnd.0 as *mut winapi::shared::windef::HWND__, &rect as *const RECT, 1);
}
}
}
最终创建了
set_check
函数:
fn set_check(listbox_hwnd: HWND, n_index: i32, n_check: i32, m_n_style: u32) {
if n_check == 2 && (m_n_style == BS_CHECKBOX as u32 || m_n_style == BS_AUTOCHECKBOX as u32) {
return;
}
unsafe {
let l_result = SendMessageW(listbox_hwnd, LB_GETITEMDATA, WPARAM(n_index as usize), LPARAM(0));
println!("{:#?}",l_result);
if l_result != LB_ERR {
let p_state: *mut AfxCheckData = if l_result == LRESULT(0) {
Box::into_raw(Box::new(AfxCheckData::new()))
} else {
// unsafe { transmute(l_result) }
Box::into_raw(Box::new(AfxCheckData::new()))
};
(*p_state).m_n_check = n_check;
if SendMessageW(listbox_hwnd, LB_SETITEMDATA, WPARAM(n_index as usize), LPARAM(p_state as isize)) == LB_ERR {
eprintln!("Failed to set item data.");
}
invalidate_check(listbox_hwnd, n_index);
}
}}
我必须删除 Transmute,因为它导致了
STATUS_ACCESS_VIOLATION
错误。
我也不明白 Transmute 的确切含义,所以我决定不使用它。
另外,如果我这样做:
let _ = PostMessageW(checklistbox_hwnd, LBN_DBLCLK, WPARAM(1), LPARAM(0));
复选框中的复选框消失。
最后,这是我从函数中得到的结果:
Item 1: Main game files - Unchecked - 198748
should have clicked
Item 2: Update DirectX - Checked - 198748
LRESULT(
400048848,
)
sent
should have clicked
选中和未选中不起作用,因为即使未选中它也会发送支票。
这是
TNewCheckListBox
的图片:
我希望这一切都有一个更简单的答案。
感谢您的阅读。
因此,按照IInspectable的建议,我决定使用
uiautomation-rs
,然后我意识到TNewCheckListBox
创建了一个或多个CheckBox
,不支持uiautomation-rs
或winapi
或给出的任何模式windows-rs
据我所知。
对于
TogglePattern
它给出了一个错误:
Failed to toggle the state: Error { code: -2146233079, message: "" }
这是一个
System.InvalidOperationException
,因此可能意味着它不支持直接切换TogglePattern
。
对于
InvokePattern
,它只是没有被直接支持。
CheckBox
也错过了HWND和类名。
它只能通过
IAccessible
访问并由 IAccIdentity
识别。
那太复杂了,现在查看 windows api 的 Rust 状态我太害怕了,不敢进入那个兔子洞。
但是,在论坛上AutoIt我发现有人提到直接发送空格键。
答案比我想象的要容易得多。
所以我就这样做了,有代码:
pub mod windows_ui_automation {
use uiautomation::{UIAutomation, UIElement};
use uiautomation::types::UIProperty::{ClassName, NativeWindowHandle, ToggleToggleState};
pub fn get_checklistbox() -> Result<UIElement, uiautomation::errors::Error>{
let automation = UIAutomation::new().unwrap();
let checklistbox_element = automation.create_matcher().classname("TNewCheckListBox");
let sec_elem = checklistbox_element.find_first();
// println!("{:#?}", sec_elem.unwrap());
sec_elem
}
pub fn get_checkboxes_from_list() {
let automation = UIAutomation::new().unwrap();
let walker = automation.get_control_view_walker().unwrap();
let checklistbox_elem = match get_checklistbox() {
Ok(elem) => elem,
Err(e) => {
println!("Failed to find checklist box: {}", e);
return; // Or handle the error appropriately but for this example no.
}
};
if let Ok(child) = walker.get_first_child(&checklistbox_elem) {
match child {
ref ch => {
process_element(ch.clone());
}
}
let mut next = child;
while let Ok(sibling) = walker.get_next_sibling(&next) {
match sibling{
ref sib => {
process_element(sib.clone());
}
}
next = sibling;
}
}
}
fn process_element(element: UIElement) {
match element {
ref el => {
let spec_classname = el.get_property_value(ClassName).unwrap(); // NULL
let spec_proc_handle = el.get_property_value(NativeWindowHandle).unwrap();
let spec_toggle_toggle_state = el.get_property_value(ToggleToggleState).unwrap(); // NULL
let spec_control_type = el.get_control_type().unwrap();
let spec_text_inside = el.get_help_text().unwrap();
println!(
"ClassName = {:#?} and HWND = {:#?} and ControlType = {:#?} and TTState = {:#?} and Help Text = {:#?}",
spec_classname.to_string(),
spec_proc_handle.to_string(),
spec_control_type.to_string(),
spec_toggle_toggle_state.to_string(),
spec_text_inside
);
match el.send_keys(" ", 0) {
Ok(_) => println!("Space key sent to element."),
Err(e) => eprintln!("Failed to send space key: {:?}", e),
}
}
}
}
}