折騰了好久,最終在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爲simple 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