1. 程式人生 > >使用IAccessible介面,遍歷DirectUI視窗控制元件的問題?

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