Общайтесь с TNewCheckBoxList установки Inno в Rust

Я пытаюсь автоматизировать установку 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:

Я надеюсь, что на все это есть более простой ответ.

Спасибо за чтение.

Автоматизация пользовательского интерфейса обычно выполняется с помощью UI Automation. Находите ли вы это проще, во многом зависит от того, понимаете ли вы COM.

IInspectable 08.08.2024 14:42

Я попробовал uiautomation-rs (обертка для Windows uiautomation, созданная leexgone), он смог найти два CheckBox, но это их тип управления, у них нет имени класса, а их дескриптор равен 0. Я использовал Inspect.exe, и он дал мне только дескриптор TNewCheckListBox: hwnd=0x0003072E ??bit class = "TNewCheckListBox" style=0x54010161 ex=0x200

Carotide 08.08.2024 21:49
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Итак, как было предложено 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),
                }
            }
        }
    }

}



Другие вопросы по теме