当前位置:编程学习 > C#/ASP.NET >>

使用IAccessible接口,遍历DirectUI窗口控件的问题?

前一段时间,做一个程序,需要完成一个小功能,即对鼠标监视,当左键单击某个文件选中时,获得该文件文件名称。
折腾了好久,最终在windowsXP下完美实现了。实现的思路是:
1、下鼠标钩子,获得鼠标左键clickup事件。
2、使用FindPointWindow,获得鼠标位置窗口句柄。
3、使用SendMessage,向该窗口(SysListView32)发送信息,获得选中文件序号及文件名称。
该程序在WindowsXP下运行正常,可以正确取出文件名。在Windows7和Server2008的桌面(也是SysListView32)也工作正常。
但是windows7和Server2008的文件浏览窗口时DirectUI,无法使用SendMessage获得相应信息。

针对DirectUI,改用IAccessible接口遍历并获得指定句柄的窗口内鼠标选中的文件。
程序getCurrentFileName,入口是指定窗口的句柄。
但是很奇怪,该程序如果从程序本身的窗体上获得句柄参数,能正常执行,找到选中文件(如从一个textbox中获得句柄值)。但是如果是用鼠标钩子和FindPointWindow获得窗口句柄值,自动调用getCurrentFileName,就无法找到选中的文件。
通过调试,可以看到AccessibleObjectFromWindow这句在两种情况下执行结果不一样。
但是不知道原因在哪里?不知道大家有没有对IAccessible接口熟悉的,帮忙分析一下?
程序如下:

#Region "使用IAccessible读取被选中的文件名称(通用于SysListView32和DirectUI两种控件)"
    ''' <summary>
    ''' 从Windows系统窗口中读取被选中的文件名称,需要考虑两种控件。
    ''' 第一种是SyslistView32,WindowsXp桌面和文件浏览窗口,Windows7、Server2008的桌面都是这种控件
    '''         syslistView32控件本身的Role值为33【列表】,每个文件Role值为34【列表项目】,ObjectType为Simple Element
    ''' 第二种是DirectUI,Windows7、Server2008的文件浏览窗口都是这种控件
    '''         DirectUI控件本身内部较为复杂,文件浏览窗口处于DirectUI较深的层次,文件浏览窗口Role值为33【列表】,每个文件Role值为34【列表项目】
    '''         但是特别注意,这里的文件的ObjectType为Container。
    ''' 两种控件都可以使用IAccessible自动化接口取访问,并且层层深入的遍历。但是特别注意,IAccessible不能进入ObjectType为Simple Element的层。
    ''' 也就是说使用IAccessible不能用AccessibleChildren进入SyslistView32的子层取遍历控件,这样会报错。这可能是自己对IAccessible接口工作原理还不是很清楚而导致的。
    ''' 
    ''' 注:这两种控件可以使用工具AccExplore进行研究和查看。
    ''' 
        Private Declare Function AccessibleObjectFromWindow Lib "oleacc" ( _
       ByVal Hwnd As Int32, _
       ByVal dwId As Int32, _
       ByRef riid As Guid, _
       <MarshalAs(UnmanagedType.IUnknown)> ByRef ppvObject As Object) As Int32
    ''' <summary>
    ''' AccessibleObjectFromWindow用于通过调用IAcessible接口,获得指定句柄为Hwnd的窗口com对象整体,将该对象置于ppvObject中,供用户访问
    ''' AccessibleObjectFromWindow如果获得了正确的Com对象,则返回值为0。如果返回值不为0,则说明获得Com对象过程中出错。
    ''' 特别注意,AccessibleObjectFromWindow只能返回Hwnd句柄指向窗口的顶级窗口。这点在Windows 7下使用最为明显。
    ''' </summary>
    Public Declare Function AccessibleChildren Lib "oleacc" ( _
        ByVal paccContainer As IAccessible, _
        ByVal iChildStart As Integer, _
        ByVal cChildren As Integer, _
         <[Out]()> ByVal rgvarChildren() As Object, _
         ByRef pcObtained As Integer) As UInt32
    ''' <summary>
    ''' AccessibleChildren用于获得当前Object即paccContainer的子Object,存放在cChildren集中
    ''' 公用变量strCurrentFileName用于返回获得的文件名
    ''' </summary>
    Public strCurrentFileName As String
    Public Sub getCurrentFileName(ByVal hwndCurrent As Int32)
        Try             
            Dim IACurrent As Accessibility.IAccessible = Nothing
            Dim ID As Int32 = 0
            Dim IID_IAcce As Guid = New Guid("618736E0-3C3D-11CF-810C-00AA00389B71")

            '调用AccessibleObjectFromWindow,获得句柄hwndCurrent指向的鼠标当前窗口所在的顶级窗口,放入IACurrent,供访问者使用
            Dim aaVal As Int32 = AccessibleObjectFromWindow(hwndCurrent, ID, IID_IAcce, IACurrent)

            'IACurrent = DirectCast(IACurrent.accParent, Accessibility.IAccessible) '不运行这条语句可以根据句柄正确获得child的数量和名称。
            Dim _ChildCount As Integer = IACurrent.accChildCount
            Dim _Children As Object() = New Object(_ChildCount) {}
            Dim _out As Integer
            '调用AccessibleChildren函数,获得当前顶级窗口(特别注意不一定是hwndCurrent所指向的窗口)的第一层子项,放入_Children
            AccessibleChildren(IACurrent, 0, _ChildCount, _Children, _out)
            '对第一层子项进行遍历
            For Each _child As Accessibility.IAccessible In _Children
                '在遍历过程中,会出现取到空子项的情况,如果取到空子项,下面程序会出错,所以需要将空子项排除
                If _child IsNot Nothing Then

                    '首先判断子项的Role值,33为【列表】,文件为34【列表项目】,我们需要找到33【列表】
                    Dim _accRole As String = _child.accRole(0)          '取当前遍历到的子项的Role
                    '如果控件是SysListView32,则:
                    '1、不能使用Accessibility.IAccessible进入SysListView32的子项
                    '2、可以使用_child.accName(i)、_chile.accState(i)、遍历该"33"列表的子项
                    '3、可以使用 _child.accSelection返回该该"33"列表中被选中的子项,但是该返回值与所期望的值不同。
                    If _accRole = "33" And strListViewClass = "SysListView32" Then
                        '找到列表后,判断当前列表是否有子项被选中。
                        PDMMainForm.LFind33.Text = "找到列表33,名称为" & _child.accName(0)
                        '如果有子项被选中, _child.accSelection返回被选中的值(该值一定大于零), 否则返回空
                        Dim _accSelected As String = _child.accSelection
                        If _accSelected > 0 Then
                            Dim i As Integer
                            i = CInt(_accSelected)
                            strCurrentFileName = _child.accName(i)
                            PDMMainForm.Lfindfile.Text = "找到选中文件,名称为" & strCurrentFileName

                            Exit Sub
                        End If
                    End If
                    '判断当前子项_child是否还有下一层子项(_child.accChildCount>0),如果有,则进行遍历
                    Dim _accCount As String = _child.accChildCount      '取当前遍历到的子项的子项数量
                    If _child.accChildCount > 0 Then
                        enumchild(_child, _child.accChildCount)
                    End If
                End If
            Next
        Catch
            '如果没有正确获得被选中的文件名称,则屏蔽try语句,进行调试
        End Try
    End Sub
    Sub enumchild(ByVal objParent As Accessibility.IAccessible, ByVal _ChildCount As Integer)
        '遍历二级及二级以下所有子控件,寻找Role为34的子控件
        Dim _accChildren As Object() = New Object(_ChildCount) {}
        Dim _out As Integer
        Try
            AccessibleChildren(objParent, 0, _ChildCount, _accChildren, _out)
        Catch
        End Try

        '遍历第n层控件
        Dim istep As Integer = 0
        Try
            '在遍历过程中,如果_accChildren为易做图 Element,则下面语句会报错,使用try语句避免
            For Each _child As Accessibility.IAccessible In _accChildren
                istep = 1
                '在遍历过程中,会出现取到空子项的情况,如果取到空子项,下面程序会出错,所以需要将空子项排除
                If _child IsNot Nothing Then
                    istep = 2
                    '首先判断子项的Role值,33为【列表】,文件为34【列表项目】,我们需要找到33【列表】
                    Dim _accRole As String = _child.accRole(0)          '取当前遍历到的子项的Role
                    istep = 3
                    If _accRole = "34" Then
                        PDMMainForm.LFind33.Text = "找到列表项34,名称为" & _child.accName(0)
                        Dim _accState As Object = _child.accState(0)
                        Const SYSTEM_MOUSEON As UInt32 = 2     ' 对象被Selection的掩码,参考oleacc.h
                        Dim iMouseOn As UInt32 = _accState And SYSTEM_MOUSEON
                        If iMouseOn = 2 Then
                            strCurrentFileName = _child.accName(0)
                            PDMMainForm.Lfindfile.Text = "找到选中文件,名称为" & strCurrentFileName
                        End If
                        With PDMMainForm.CB33list
                            .Items.Add(_child.accName(0) & "," & _accState & "," & iMouseOn)
                        End With
                    End If
                    
                    Dim _accCount As String = _child.accChildCount      '取当前遍历到的子项的子项数量
                    If _accCount > 0 Then
                        enumchild(_child, _accCount)
                    End If
                End If

            Next
        Catch
        End Try
    End Sub
补充:.NET技术 ,  VB.NET
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,