Я пытаюсь автоматизировать установку 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, аналогично CCheckListBox::SetCheck(int nIndex, int nCheck)
, найденному в исходном коде winscp.
Вот и все, прошу прощения, если код плохой, потому что я все еще новичок в Rust, а преобразование некоторого кода C++, содержащего Windows API, сложно и оно не является полным.
Я определил структуры здесь:
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,
}
}
}
Затем я попытался сделать validate_check как CCheckListBox::InvalidateCheck(int nIndex)
для обработки ошибок, если я правильно понял.
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);
}
}}
Мне пришлось удалить трансмутацию, потому что она вызывала STATUS_ACCESS_VIOLATION
ошибки.
Я также не понимал точного значения слова «трансмутация», поэтому решил не использовать его.
Кроме того, если я сделаю это:
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
:
Я надеюсь, что на все это есть более простой ответ.
Спасибо за чтение.
Я попробовал uiautomation-rs
(обертка для Windows uiautomation, созданная leexgone), он смог найти два CheckBox
, но это их тип управления, у них нет имени класса, а их дескриптор равен 0. Я использовал Inspect.exe, и он дал мне только дескриптор TNewCheckListBox: hwnd=0x0003072E ??bit class = "TNewCheckListBox" style=0x54010161 ex=0x200
Итак, как было предложено 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
.
Это было бы слишком сложно, и, глядя на состояние API Windows на наличие ржавчины прямо сейчас, я был слишком напуган, чтобы идти в эту кроличью нору.
Однако на форуме 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),
}
}
}
}
}
Автоматизация пользовательского интерфейса обычно выполняется с помощью UI Automation. Находите ли вы это проще, во многом зависит от того, понимаете ли вы COM.