使用IAccessible介面,遍歷DirectUI視窗控制元件的問題?
阿新 • • 發佈:2019-02-14
前一段時間,做一個程式,需要完成一個小功能,即對滑鼠監視,當左鍵單擊某個檔案選中時,獲得該檔案檔名稱。
折騰了好久,最終在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
折騰了好久,最終在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