Using UIAutomation to automate the Windows 10 Calculator

John_w

MrExcel MVP
Joined
Oct 15, 2007
Messages
7,003
As a learning exercise, I decided to write VBA code to automate the Windows 10 Calculator using the UIAutomation library, after seeing this thread which controls the old Windows 7 Calc.exe program:


In the code below, Click_Keys is the main routine which clicks the keys, which are specified in the 'keys' string argument. This string can contain any of the following characters and special key strings surrounded by '|':

Number0 1 2 3 4 5 6 7 8 9
Arithmetic/ X - + =
Other. % |+-|
Function|RECIP| |SQR| |SQRT|
Clear|CE| |C| |BS|
Memory|MC| |MR| |M+| |M-| |MS|

The code works with the Calculator in standard mode and extracts the result and expression.

The code also works if the Calculator is displayed as 'Keep on top', in which case the memory keys aren't available and will have no effect if they are specified in the 'keys' argument.

The test routine extracts the Calculator result and expression and displays them in a message box. Note that the expression may contain non-ANSI Unicode characters (e.g. the square root symbol) therefore we use the MessageBoxW API function because the normal VBA MsgBox displays such characters as '?'.

The code requires references to Microsoft Scripting Runtime and UIAutomationClient.

Standard module:
VBA Code:
'References required:
'Microsoft Scripting Runtime
'UIAutomationClient

Option Explicit

#If VBA7 Then
    Private Declare PtrSafe Function MessageBoxW Lib "User32" (ByVal hWnd As LongPtr, ByVal lpText As LongPtr, ByVal lpCaption As LongPtr, ByVal uType As Long) As Long
    Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#Else
    Private Declare Function MessageBoxW Lib "User32" (ByVal hWnd As Long, ByVal lpText As Long, ByVal lpCaption As Long, ByVal uType As Long) As Long
    Private Declare Sub Sleep Lib "kernel32" (ByVal milliseconds As Long)
#End If


Public Sub Test_Automate_Calculator()

    #If VBA7 Then
        Dim CalcHwnd As LongPtr
    #Else
        Dim CalcHwnd As Long
    #End If
    Dim keypadDict As Scripting.Dictionary
    Dim CalculatorResult As String
    Dim CalculatorExpression As String
    
    CalcHwnd = Find_Calculator()
    
    If CalcHwnd <> 0 Then
        Set keypadDict = Build_Keys_Dict(CalcHwnd)
        Click_Keys "|C|3.6+5=|SQRT||RECIP|=", CalcHwnd, keypadDict
        CalculatorResult = Get_Result(CalcHwnd)
        CalculatorExpression = Get_Expression(CalcHwnd)
        MsgBoxW "Result:  " & CalculatorResult & vbCrLf & _
                "Expression: " & CalculatorExpression
    Else
        MsgBox "Calculator isn't running"
    End If
    
End Sub


Public Function MsgBoxW(Prompt As String, Optional Buttons As VbMsgBoxStyle = vbOKOnly, Optional Title As String = "Microsoft Excel") As VbMsgBoxResult
    Prompt = Prompt & vbNullChar 'Add null terminators
    Title = Title & vbNullChar
    MsgBoxW = MessageBoxW(Application.hWnd, StrPtr(Prompt), StrPtr(Title), Buttons)
End Function


#If VBA7 Then
Public Function Find_Calculator() As LongPtr
#Else
Public Function Find_Calculator() As Long
#End If
   
    'Find the Calculator window and return its window handle

    Dim UIAuto As IUIAutomation
    Dim Desktop As IUIAutomationElement
    Dim CalcWindow As IUIAutomationElement
    Dim ControlTypeAndNameCond As IUIAutomationCondition
    Dim WindowPattern As IUIAutomationWindowPattern
    
    Find_Calculator = 0

    'Create UIAutomation object
    
    Set UIAuto = New CUIAutomation
    
    'Conditions to find the main Calculator window on the Desktop
    'ControlType:   UIA_WindowControlTypeId (0xC370)
    'Name:          "Calculator"
    
    With UIAuto
        Set Desktop = .GetRootElement
        Set ControlTypeAndNameCond = .CreateAndCondition(.CreatePropertyCondition(UIA_ControlTypePropertyId, UIA_WindowControlTypeId), _
                                                         .CreatePropertyCondition(UIA_NamePropertyId, "Calculator"))
    End With
    Set CalcWindow = Desktop.FindFirst(TreeScope_Children, ControlTypeAndNameCond)
    
    If Not CalcWindow Is Nothing Then
    
        'Restore the Calculator window, because it must not be minimised (off screen/iconic) in order to find the keypad keys
        
        If CalcWindow.CurrentIsOffscreen Then
            Set WindowPattern = CalcWindow.GetCurrentPattern(UIA_WindowPatternId)
            WindowPattern.SetWindowVisualState WindowVisualState.WindowVisualState_Normal
            DoEvents
            Sleep 100
        End If
        
        'Return the Calculator's window handle
        
        Find_Calculator = CalcWindow.GetCurrentPropertyValue(UIA_NativeWindowHandlePropertyId)
                
    End If

End Function


#If VBA7 Then
Public Function Build_Keys_Dict(CalcHwnd As LongPtr) As Scripting.Dictionary
#Else
Public Function Build_Keys_Dict(CalcHwnd As Long) As Scripting.Dictionary
#End If

    'Create a dictionary which maps each keypad key to its UI automation element via the AutomationId string
    
    Dim keysMapping As Variant
    Dim i As Long
    Dim key As cKey
    
    keysMapping = Split("0,num0Button,1,num1Button,2,num2Button,3,num3Button,4,num4Button,5,num5Button,6,num6Button,7,num7Button,8,num8Button,9,num9Button," & _
                        ".,decimalSeparatorButton,/,divideButton,X,multiplyButton,-,minusButton,+,plusButton,=,equalButton,%,percentButton," & _
                        "|+-|,negateButton,|RECIP|,invertButton,|SQR|,xpower2Button,|SQRT|,squareRootButton," & _
                        "|CE|,clearEntryButton,|C|,clearButton,|BS|,backSpaceButton," & _
                        "|MC|,ClearMemoryButton,|MR|,MemRecall,|M+|,MemPlus,|M-|,MemMinus,|MS|,memButton", ",")
    
    Set Build_Keys_Dict = New Scripting.Dictionary
   
    For i = 0 To UBound(keysMapping) Step 2
        Set key = New cKey
        key.keypadKey = keysMapping(i)
        Set key.UIelement = Find_Key(CalcHwnd, CStr(keysMapping(i + 1)))
        Build_Keys_Dict.Add keysMapping(i), key
    Next

End Function


#If VBA7 Then
Private Function Find_Key(CalcHwnd As LongPtr, keyAutomationId As String) As IUIAutomationElement
#Else
Private Function Find_Key(CalcHwnd As Long, keyAutomationId As String) As IUIAutomationElement
#End If

    'Find the specified Calculator key by its AutomationId
    
    Dim UIAuto As IUIAutomation
    Dim Calc As IUIAutomationElement
    Dim KeyCond As IUIAutomationCondition
    
    'Get the Calculator automation element from its window handle
    
    Set UIAuto = New CUIAutomation
    Set Calc = UIAuto.ElementFromHandle(ByVal CalcHwnd)
    
    'Condition to find the specified Calculator key, for example
    'AutomationId:   "num3Button"
    
    Set KeyCond = UIAuto.CreatePropertyCondition(UIA_AutomationIdPropertyId, keyAutomationId)
    
    'Must use TreeScope_Descendants to find the keypad keys, rather than TreeScope_Children, because the Calculator keys are not immediate children of the Calculator window.
    'TreeScope_Descendants searches the element's descendants, including children.  TreeScope_Children searches only the element's immediate children.
    'Note that the memory keys don't exist if the Calculator is in 'Keep on top' mode
    
    Set Find_Key = Calc.FindFirst(TreeScope_Descendants, KeyCond)
    
End Function


#If VBA7 Then
Public Sub Click_Keys(keys As String, CalcHwnd As LongPtr, Keypad_Dict As Dictionary)
#Else
Public Sub Click_Keys(keys As String, CalcHwnd As Long, Keypad_Dict As Dictionary)
#End If

    'Automate the Calculator by clicking the specified keys

    Dim UIAuto As IUIAutomation
    Dim Calc As IUIAutomationElement
    Dim InvokePattern As IUIAutomationInvokePattern
    Dim i As Long, p As Long
    Dim thisKey As String
    Dim key As cKey
    
    'Get the Calculator automation element from its window handle
    
    Set UIAuto = New CUIAutomation
    Set Calc = UIAuto.ElementFromHandle(ByVal CalcHwnd)
    
    'Parse the keys string, looking up each key in the keypad dictionary and clicking the key via its UIAutomation element
    
    For i = 1 To Len(keys)
    
        thisKey = UCase(Mid(keys, i, 1))
        If thisKey = "|" Then
            'Special key surrounded by "|"
            p = InStr(i + 1, keys, "|")
            thisKey = Mid(keys, i, p + 1 - i)
            i = p
        End If
        
        If Keypad_Dict.Exists(thisKey) Then
            Set key = Keypad_Dict(thisKey)
            Set InvokePattern = key.UIelement.GetCurrentPattern(UIA_InvokePatternId)
            InvokePattern.Invoke
            DoEvents
            Sleep 100
        Else
            MsgBox "Key '" & thisKey & "' not found in keypad dictionary. Check syntax of keys argument", vbExclamation
        End If
        
    Next
        
End Sub


#If VBA7 Then
Public Function Get_Result(CalcHwnd As LongPtr) As String
#Else
Public Function Get_Result(CalcHwnd As Long) As String
#End If

    'Extract the Calculator result string
    
    Dim UIAuto As IUIAutomation
    Dim Calc As IUIAutomationElement
    Dim ResultCond As IUIAutomationCondition
    Dim Result As IUIAutomationElement
    
    'Get the Calculator automation element from its window handle
    
    Set UIAuto = New CUIAutomation
    Set Calc = UIAuto.ElementFromHandle(ByVal CalcHwnd)
    
    'Condition to find the Calculator results
    'Name:   "Display is 7.82842712474619"
    'AutomationId:   "CalculatorResults"
    
    Set ResultCond = UIAuto.CreatePropertyCondition(UIA_AutomationIdPropertyId, "CalculatorResults")
    Set Result = Calc.FindFirst(TreeScope_Descendants, ResultCond)
    
    If Result Is Nothing Then
        Set ResultCond = UIAuto.CreatePropertyCondition(UIA_AutomationIdPropertyId, "CalculatorAlwaysOnTopResults")
        Set Result = Calc.FindFirst(TreeScope_Descendants, ResultCond)
    End If
    
    Get_Result = Mid(Result.CurrentName, InStr(Result.CurrentName, " is ") + Len(" is "))
    
End Function


#If VBA7 Then
Public Function Get_Expression(CalcHwnd As LongPtr) As String
#Else
Public Function Get_Expression(CalcHwnd As Long) As String
#End If

    'Extract the Calculator expression string

    Dim UIAuto As IUIAutomation
    Dim Calc As IUIAutomationElement
    Dim ExpressionCond As IUIAutomationCondition
    Dim Expression As IUIAutomationElement
    
    'Get the IE automation element from its window handle
    
    Set UIAuto = New CUIAutomation
    Set Calc = UIAuto.ElementFromHandle(ByVal CalcHwnd)
    
    'Condition to find the Calculator expression, if it exists
    'Name:   "Expression is ?(8) + 5="
    'AutomationId:   "CalculatorExpression"
    
    Set ExpressionCond = UIAuto.CreatePropertyCondition(UIA_AutomationIdPropertyId, "CalculatorExpression")
    
    Set Expression = Calc.FindFirst(TreeScope_Descendants, ExpressionCond)
    
    If Not Expression Is Nothing Then
        Get_Expression = Mid(Expression.CurrentName, InStr(Expression.CurrentName, " is ") + Len(" is "))
    Else
        Get_Expression = ""
    End If
    
End Function
Class module, named cKey:
VBA Code:
Option Explicit

Public keypadKey As String
Public UIelement As IUIAutomationElement
 

Excel Facts

Save Often
If you start asking yourself if now is a good time to save your Excel workbook, the answer is Yes

Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
8,308
Office Version
  1. 2016
Platform
  1. Windows
Hi John,

The code doesn't work for me. It seems it doesn't find the Calculator window ...The following line always evaluates to Nothing.
VBA Code:
Set CalcWindow = Desktop.FindFirst(TreeScope_Children, ControlTypeAndNameCond)

I have the windows 10 calculator dispalyed (in Standard mode) and maximized (always visible) before running the Test_Automate_Calculator routine.

Obviously, I have a reference set to both, Microsoft Scripting Runtime and UIAutomationClient libraries.

Thanks.
 

John_w

MrExcel MVP
Joined
Oct 15, 2007
Messages
7,003
Thanks for trying the code Jaafar.

I wonder if it's due to language differences? That line is looking for a window control with a Name of "Calculator":

1592652298009.png


Is your window also named "Calculator"?
 

Yongle

Well-known Member
Joined
Mar 11, 2015
Messages
6,977
Office Version
  1. 365
Platform
  1. Windows
@John_w
I gave it a go, with the calculator app both closed and pre-opened
On both attempts it simply told me that the "Calculator isn't running"
 

Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
8,308
Office Version
  1. 2016
Platform
  1. Windows

ADVERTISEMENT

Thanks for trying the code Jaafar.

I wonder if it's due to language differences? That line is looking for a window control with a Name of "Calculator":

View attachment 16643

Is your window also named "Calculator"?

Cool!

Yes. it wasn't working due to language differences. I replaced "Calculator" with FRENCH "Calculatrice" and now it works.

The other day when I run a quick test with inspect.exe, I thought that Windows10 Calculator coudn't be accessed via UIAutomation but I was wrong, I didn't look properly or perhaps I was looking at MSAA which doesn't seem to work.

Thanks for sharing this.
 

John_w

MrExcel MVP
Joined
Oct 15, 2007
Messages
7,003
@John_w
I gave it a go, with the calculator app both closed and pre-opened
On both attempts it simply told me that the "Calculator isn't running"
Try this complete replacement for the standard module. This calls an alternative function, Find_Calculator2, which uses Windows API functions, rather than UIAutomation, to find and restore the Calculator window.
VBA Code:
'References required:
'Microsoft Scripting Runtime
'UIAutomationClient

Option Explicit

Private Type POINTAPI
    x As Long
    y As Long
End Type

Private Type RECT
    left As Long
    top As Long
    right As Long
    bottom As Long
End Type

Private Type WINDOWPLACEMENT
    length As Long
    flags As Long
    showCmd As Long
    ptMinPosition As POINTAPI
    ptMaxPosition As POINTAPI
    rcNormalPosition As RECT
End Type

#If VBA7 Then
    Private Declare PtrSafe Function MessageBoxW Lib "user32" (ByVal hWnd As LongPtr, ByVal lpText As LongPtr, ByVal lpCaption As LongPtr, ByVal uType As Long) As Long
    Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
    Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As Any, ByVal lpWindowName As Any) As LongPtr
    Private Declare PtrSafe Function IsIconic Lib "user32.dll" (ByVal hWnd As LongPtr) As Long
    Private Declare PtrSafe Function GetWindowPlacement Lib "user32" (ByVal hWnd As LongPtr, ByRef lpwndpl As WINDOWPLACEMENT) As Long
    Private Declare PtrSafe Function SetWindowPlacement Lib "user32" (ByVal hWnd As LongPtr, ByRef lpwndpl As WINDOWPLACEMENT) As Long
#Else
    Private Declare Function MessageBoxW Lib "user32" (ByVal hWnd As Long, ByVal lpText As Long, ByVal lpCaption As Long, ByVal uType As Long) As Long
    Private Declare Sub Sleep Lib "kernel32" (ByVal milliseconds As Long)
    Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As Any, ByVal lpWindowName As Any) As Long
    Private Declare Function IsIconic Lib "user32.dll" (ByVal hWnd As Long) As Long
    Private Declare Function GetWindowPlacement Lib "user32" (ByVal hWnd As Long, ByRef lpwndpl As WINDOWPLACEMENT) As Long
    Private Declare Function SetWindowPlacement Lib "user32" (ByVal hWnd As Long, ByRef lpwndpl As WINDOWPLACEMENT) As Long
#End If

Private Const SW_SHOWNORMAL As Long = 1


Public Sub Test_Automate_Calculator()

    #If VBA7 Then
        Dim CalcHwnd As LongPtr
    #Else
        Dim CalcHwnd As Long
    #End If
    Dim keypadDict As Scripting.Dictionary
    Dim CalculatorResult As String
    Dim CalculatorExpression As String
    
    'CalcHwnd = Find_Calculator()    'using UIAutomation
    CalcHwnd = Find_Calculator2()   'using Windows API
    
    If CalcHwnd <> 0 Then
        Set keypadDict = Build_Keys_Dict(CalcHwnd)
        Click_Keys "|C|3.6+5=|SQRT||RECIP|=", CalcHwnd, keypadDict
        CalculatorResult = Get_Result(CalcHwnd)
        CalculatorExpression = Get_Expression(CalcHwnd)
        MsgBoxW "Result:  " & CalculatorResult & vbCrLf & _
                "Expression: " & CalculatorExpression
    Else
        MsgBox "Calculator isn't running"
    End If
    
End Sub


Public Function MsgBoxW(Prompt As String, Optional Buttons As VbMsgBoxStyle = vbOKOnly, Optional Title As String = "Microsoft Excel") As VbMsgBoxResult
    Prompt = Prompt & vbNullChar 'Add null terminators
    Title = Title & vbNullChar
    MsgBoxW = MessageBoxW(Application.hWnd, StrPtr(Prompt), StrPtr(Title), Buttons)
End Function


#If VBA7 Then
Public Function Find_Calculator2() As LongPtr
#Else
Public Function Find_Calculator2() As Long
#End If

    Dim wp As WINDOWPLACEMENT

    Find_Calculator2 = FindWindow("ApplicationFrameWindow", "Calculator")
    
    If IsIconic(Find_Calculator2) Then
        'Restore the Calculator window, because it must not be minimised (off screen/iconic) in order to find the keypad keys
        wp.Length = Len(wp)
        GetWindowPlacement Find_Calculator2, wp
        wp.showCmd = SW_SHOWNORMAL
        SetWindowPlacement Find_Calculator2, wp
    End If

End Function


#If VBA7 Then
Public Function Find_Calculator() As LongPtr
#Else
Public Function Find_Calculator() As Long
#End If
   
    'Find the Calculator window and return its window handle

    Dim UIAuto As IUIAutomation
    Dim Desktop As IUIAutomationElement
    Dim CalcWindow As IUIAutomationElement
    Dim ControlTypeAndNameCond As IUIAutomationCondition
    Dim WindowPattern As IUIAutomationWindowPattern
    
    Find_Calculator = 0

    'Create UIAutomation object
    
    Set UIAuto = New CUIAutomation
    
    'Conditions to find the main Calculator window on the Desktop
    'ControlType:   UIA_WindowControlTypeId (0xC370)
    'Name:          "Calculator"
    
    With UIAuto
        Set Desktop = .GetRootElement
        Set ControlTypeAndNameCond = .CreateAndCondition(.CreatePropertyCondition(UIA_ControlTypePropertyId, UIA_WindowControlTypeId), _
                                                         .CreatePropertyCondition(UIA_NamePropertyId, "Calculator"))
    End With
    Set CalcWindow = Desktop.FindFirst(TreeScope_Children, ControlTypeAndNameCond)
    
    If Not CalcWindow Is Nothing Then
    
        'Restore the Calculator window, because it must not be minimised (off screen/iconic) in order to find the keypad keys
        
        If CalcWindow.CurrentIsOffscreen Then
            Set WindowPattern = CalcWindow.GetCurrentPattern(UIA_WindowPatternId)
            WindowPattern.SetWindowVisualState WindowVisualState.WindowVisualState_Normal
            DoEvents
            Sleep 100
        End If
        
        'Return the Calculator's window handle
        
        Find_Calculator = CalcWindow.GetCurrentPropertyValue(UIA_NativeWindowHandlePropertyId)
                
    End If

End Function


#If VBA7 Then
Public Function Build_Keys_Dict(CalcHwnd As LongPtr) As Scripting.Dictionary
#Else
Public Function Build_Keys_Dict(CalcHwnd As Long) As Scripting.Dictionary
#End If

    'Create a dictionary which maps each keypad key to its UI automation element via the AutomationId string
    
    Dim keysMapping As Variant
    Dim i As Long
    Dim key As cKey
    
    keysMapping = Split("0,num0Button,1,num1Button,2,num2Button,3,num3Button,4,num4Button,5,num5Button,6,num6Button,7,num7Button,8,num8Button,9,num9Button," & _
                        ".,decimalSeparatorButton,/,divideButton,X,multiplyButton,-,minusButton,+,plusButton,=,equalButton,%,percentButton," & _
                        "|+-|,negateButton,|RECIP|,invertButton,|SQR|,xpower2Button,|SQRT|,squareRootButton," & _
                        "|CE|,clearEntryButton,|C|,clearButton,|BS|,backSpaceButton," & _
                        "|MC|,ClearMemoryButton,|MR|,MemRecall,|M+|,MemPlus,|M-|,MemMinus,|MS|,memButton", ",")
    
    Set Build_Keys_Dict = New Scripting.Dictionary
   
    For i = 0 To UBound(keysMapping) Step 2
        Set key = New cKey
        key.keypadKey = keysMapping(i)
        Set key.UIelement = Find_Key(CalcHwnd, CStr(keysMapping(i + 1)))
        Build_Keys_Dict.Add keysMapping(i), key
    Next

End Function


#If VBA7 Then
Private Function Find_Key(CalcHwnd As LongPtr, keyAutomationId As String) As IUIAutomationElement
#Else
Private Function Find_Key(CalcHwnd As Long, keyAutomationId As String) As IUIAutomationElement
#End If

    'Find the specified Calculator key by its AutomationId
    
    Dim UIAuto As IUIAutomation
    Dim Calc As IUIAutomationElement
    Dim KeyCond As IUIAutomationCondition
    
    'Get the Calculator automation element from its window handle
    
    Set UIAuto = New CUIAutomation
    Set Calc = UIAuto.ElementFromHandle(ByVal CalcHwnd)
    
    'Condition to find the specified Calculator key, for example
    'AutomationId:   "num3Button"
    
    Set KeyCond = UIAuto.CreatePropertyCondition(UIA_AutomationIdPropertyId, keyAutomationId)
    
    'Must use TreeScope_Descendants to find the keypad keys, rather than TreeScope_Children, because the Calculator keys are not immediate children of the Calculator window.
    'TreeScope_Descendants searches the element's descendants, including children.  TreeScope_Children searches only the element's immediate children.
    'Note that the memory keys don't exist if the Calculator is in 'Keep on top' mode
    
    Set Find_Key = Calc.FindFirst(TreeScope_Descendants, KeyCond)
    
End Function


#If VBA7 Then
Public Sub Click_Keys(keys As String, CalcHwnd As LongPtr, Keypad_Dict As Dictionary)
#Else
Public Sub Click_Keys(keys As String, CalcHwnd As Long, Keypad_Dict As Dictionary)
#End If

    'Automate the Calculator by clicking the specified keys

    Dim UIAuto As IUIAutomation
    Dim Calc As IUIAutomationElement
    Dim InvokePattern As IUIAutomationInvokePattern
    Dim i As Long, p As Long
    Dim thisKey As String
    Dim key As cKey
    
    'Get the Calculator automation element from its window handle
    
    Set UIAuto = New CUIAutomation
    Set Calc = UIAuto.ElementFromHandle(ByVal CalcHwnd)
    
    'Parse the keys string, looking up each key in the keypad dictionary and clicking the key via its UIAutomation element
    
    For i = 1 To Len(keys)
    
        thisKey = UCase(Mid(keys, i, 1))
        If thisKey = "|" Then
            'Special key surrounded by "|"
            p = InStr(i + 1, keys, "|")
            thisKey = Mid(keys, i, p + 1 - i)
            i = p
        End If
        
        If Keypad_Dict.Exists(thisKey) Then
            Set key = Keypad_Dict(thisKey)
            Set InvokePattern = key.UIelement.GetCurrentPattern(UIA_InvokePatternId)
            InvokePattern.Invoke
            DoEvents
            Sleep 100
        Else
            MsgBox "Key '" & thisKey & "' not found in keypad dictionary. Check syntax of keys argument", vbExclamation
        End If
        
    Next
        
End Sub


#If VBA7 Then
Public Function Get_Result(CalcHwnd As LongPtr) As String
#Else
Public Function Get_Result(CalcHwnd As Long) As String
#End If

    'Extract the Calculator result string
    
    Dim UIAuto As IUIAutomation
    Dim Calc As IUIAutomationElement
    Dim ResultCond As IUIAutomationCondition
    Dim Result As IUIAutomationElement
    
    'Get the Calculator automation element from its window handle
    
    Set UIAuto = New CUIAutomation
    Set Calc = UIAuto.ElementFromHandle(ByVal CalcHwnd)
    
    'Condition to find the Calculator results
    'Name:   "Display is 7.82842712474619"
    'AutomationId:   "CalculatorResults"
    
    Set ResultCond = UIAuto.CreatePropertyCondition(UIA_AutomationIdPropertyId, "CalculatorResults")
    Set Result = Calc.FindFirst(TreeScope_Descendants, ResultCond)
    
    If Result Is Nothing Then
        Set ResultCond = UIAuto.CreatePropertyCondition(UIA_AutomationIdPropertyId, "CalculatorAlwaysOnTopResults")
        Set Result = Calc.FindFirst(TreeScope_Descendants, ResultCond)
    End If
    
    Get_Result = Mid(Result.CurrentName, InStr(Result.CurrentName, " is ") + Len(" is "))
    
End Function


#If VBA7 Then
Public Function Get_Expression(CalcHwnd As LongPtr) As String
#Else
Public Function Get_Expression(CalcHwnd As Long) As String
#End If

    'Extract the Calculator expression string

    Dim UIAuto As IUIAutomation
    Dim Calc As IUIAutomationElement
    Dim ExpressionCond As IUIAutomationCondition
    Dim Expression As IUIAutomationElement
    
    'Get the IE automation element from its window handle
    
    Set UIAuto = New CUIAutomation
    Set Calc = UIAuto.ElementFromHandle(ByVal CalcHwnd)
    
    'Condition to find the Calculator expression, if it exists
    'Name:   "Expression is ?(8) + 5="
    'AutomationId:   "CalculatorExpression"
    
    Set ExpressionCond = UIAuto.CreatePropertyCondition(UIA_AutomationIdPropertyId, "CalculatorExpression")
    
    Set Expression = Calc.FindFirst(TreeScope_Descendants, ExpressionCond)
    
    If Not Expression Is Nothing Then
        Get_Expression = Mid(Expression.CurrentName, InStr(Expression.CurrentName, " is ") + Len(" is "))
    Else
        Get_Expression = ""
    End If
    
End Function
 

John_w

MrExcel MVP
Joined
Oct 15, 2007
Messages
7,003

ADVERTISEMENT

Cool!

Yes. it wasn't working due to language differences. I replaced "Calculator" with FRENCH "Calculatrice" and now it works.

The other day when I run a quick test with inspect.exe, I thought that Windows10 Calculator coudn't be accessed via UIAutomation but I was wrong, I didn't look properly or perhaps I was looking at MSAA which doesn't seem to work.

Thanks for sharing this.
Excellent! Thanks for checking.
 

Yongle

Well-known Member
Joined
Mar 11, 2015
Messages
6,977
Office Version
  1. 365
Platform
  1. Windows
in reply to post#6

Code fails at this line
VBA Code:
Set InvokePattern = key.UIelement.GetCurrentPattern(UIA_InvokePatternId)

Error 91.jpg
 

John_w

MrExcel MVP
Joined
Oct 15, 2007
Messages
7,003
The Find_Key function probably didn't find the key with the specified keyAutomationId argument, leading to the above error in Click_Keys. Add this diagnostic at the bottom of Find_Key:

VBA Code:
    If Find_Key Is Nothing Then
        Debug.Print "AutomationId '" & keyAutomationId & "' not found"
        Stop
    End If
The AutomationIds are every second string in the keysMapping array:
VBA Code:
   keysMapping = Split("0,num0Button,1,num1Button,2,num2Button,3,num3Button,4,num4Button,5,num5Button,6,num6Button,7,num7Button,8,num8Button,9,num9Button," & _
                        ".,decimalSeparatorButton,/,divideButton,X,multiplyButton,-,minusButton,+,plusButton,=,equalButton,%,percentButton," & _
                        "|+-|,negateButton,|RECIP|,invertButton,|SQR|,xpower2Button,|SQRT|,squareRootButton," & _
                        "|CE|,clearEntryButton,|C|,clearButton,|BS|,backSpaceButton," & _
                        "|MC|,ClearMemoryButton,|MR|,MemRecall,|M+|,MemPlus,|M-|,MemMinus,|MS|,memButton", ",")
In your case I suspect Find_Key isn't finding any of the above AutomationIds.
 

Forum statistics

Threads
1,141,575
Messages
5,707,173
Members
421,495
Latest member
jono_oh

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Top