与 Rust 中 Inno 设置的 TNewCheckBoxList 进行通信

问题描述 投票:0回答:1

我正在尝试自动安装 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
的图片: Picture of the TNewCheckListBox

我希望这一切都有一个更简单的答案。

感谢您的阅读。

winapi rust inno-setup ui-automation windows-rs
1个回答
0
投票

因此,按照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),
                }
            }
        }
    }

}



© www.soinside.com 2019 - 2024. All rights reserved.