使用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
發佈了51 篇原創文章 · 獲贊 43 · 訪問量 61萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章