I am trying to automate the installation of an Inno Setup, I have already been able to communicate with a Button and a Checkbox but I have been stuck for communication with a TNewCheckListBox
. If I understood the docs correctly, each TNewCheckBox
are created dynamically but I haven't been able to communicate at all with them nor even see them.
I have found a way to get their text, however I cannot get to click on them or to setcheck.
I tried the implementation of the CLBN_CHKCHANGE
but to no avail.
There is a part of the code :
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
}
Here I try to get the the checkboxes handles but it does not work, I defined the CLBN_CHKCHANGE
previously through this constant:
const CLBN_CHKCHANGE: u32 = 40;
and I also tried this const CLBN_CHKCHANGE: u32 = 0x00000040;
I tried a more complex usage to set_check similarly to the CCheckListBox::SetCheck(int nIndex, int nCheck)
found inside the winscp source code.
There it is, I apologize if the code is bad because I am still new to Rust and the conversion of some C++ code that contains the windows API is difficult and it is not full.
I defined the structs here :
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,
}
}
}
Then I tried to to make the invalidate_check like the CCheckListBox::InvalidateCheck(int nIndex)
to handle errors if I understood correctly.
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);
}
}
}
And finally created the set_check
function :
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);
}
}}
I had to remove transmute because it caused STATUS_ACCESS_VIOLATION
errors.
I also didn't understand the exact meaning of transmute so I decided not to use it.
Also, if I do this :
let _ = PostMessageW(checklistbox_hwnd, LBN_DBLCLK, WPARAM(1), LPARAM(0));
The checkboxes from the checklistbox disappear.
Finally, this is the results I get from the function :
Item 1: Main game files - Unchecked - 198748
should have clicked
Item 2: Update DirectX - Checked - 198748
LRESULT(
400048848,
)
sent
should have clicked
The checked and unchecked do not work since even if it is unchecked it will send check.
Here is a picture of the TNewCheckListBox
:
I hope there is an easier answer for all of that.
Thank you for reading.
So as proposed by IInspectable, I decided to use uiautomation-rs
and I then realized that the TNewCheckListBox
creates one or multiple CheckBox
that do not support any Pattern given by uiautomation-rs
nor winapi
nor windows-rs
to my knowledge.
For the TogglePattern
it gave an Error :
Failed to toggle the state: Error { code: -2146233079, message: "" }
Which is a System.InvalidOperationException
, thus probably meaning it does not support direct toggling through TogglePattern
.
For the InvokePattern
it was just not supported directly.
The CheckBox
also miss the HWND and Classname.
It is uniquely accessible by the IAccessible
and identified by IAccIdentity
.
That would be too complicated and viewing the state of the windows api for rust right now I was too scared to go into that rabbit hole.
However, on the forum AutoIt I found someone mentioning directly sending the space key.
The answer was way easier than I thought it would have been.
So I just did that, there is the code :
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),
}
}
}
}
}