使用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