I have a macro that does some actions using the UI Automation
framework. I would like to suppress the rapid movements on the ribbon during the macro execution and w
It seems like there is a Windows API function that some people suggest: LockWindowUpdate
. The main problem is that it seems unreliable based on the tests that I've done and the 2 posts that I've found:
LockWindowUpdate is not intended for general-purpose suppression of window redraw. The purpose of the LockWindowUpdate function is to permit drag/drop feedback to be drawn over a window without interference from the window itself. The intent is that the window is locked when feedback is drawn and unlocked when feedback is complete.
But if you want to test it for yourself, it can be declared at the top of your module with the following backward compatible declaration construct:
'LockWindowUpdate declaration
#If VBA7 Then
Private Declare PtrSafe Function LockWindowUpdate Lib "user32" (ByVal hWndLock As LongPtr) As Long
#Else
Private Declare Function LockWindowUpdate Lib "user32" (ByVal hWndLock As Long) As Long
#End If
So, by supplying the window handle of the workbook you are interacting with (must be the top window), you may be able to temporarily freeze that Window. For example you could add:
'WindowHandle declaration
#If VBA7 Then
Private WindowHandle As LongPtr
#Else
Private WindowHandle As Long
#End If
'GetForegroundWindow declaration
#If VBA7 Then
Private Declare PtrSafe Function GetForegroundWindow Lib "USER32" () As LongPtr
#Else
Private Declare Function GetForegroundWindow Lib "user32" () As Long
#End If
Sub LockWindow()
On Error GoTo ErrHandler
WindowHandle = GetForegroundWindow
LockWindowUpdate WindowHandle
'Your code here
ErrHandler:
LockWindowUpdate WindowHandle
End Sub
There is also a message that you can send via the SendMessage Window API function that is exaclty made for preventing a window to be redrawn or refreshed.
To implement this for an Excel workbook, you could use the following declaration of the SendMessage
function, the GetForegroundWindow
(see code from above) and the appropriate constant:
'SendMessage declaration
#If VBA7 Then
Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
#Else
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Long) As Long
#End If
'SendMessage Message(s)
Private Const WM_SETREDRAW = &HB
You would then surround your code like in the following example:
Sub FreezeWorkbook()
On Error GoTo ErrHandler
WindowHandle = GetForegroundWindow
Call SendMessage(WindowHandle, WM_SETREDRAW, False, 0&)
'Your code here
ErrHandler:
Call SendMessage(WindowHandle, WM_SETREDRAW, True, 0&)
End Sub
I haven't been able to test it with UI Automation commands, but it seems like it should work.
If that doesn't work, you might want to consider doing it for the screen as a whole. For that you'll need a special function that returns the handle for the desktop:
'GetDesktopWindow declaration
#If VBA7 Then
Private Declare PtrSafe Function GetDesktopWindow Lib "user32" () As LongPtr
#Else
Private Declare Function GetDesktopWindow Lib "user32" () As Long
#End If
And the rest is basically the same:
Sub FreezeDesktop()
On Error GoTo ErrHandler
Call SendMessage(GetDesktopWindow, WM_SETREDRAW, False, 0&)
'Your code here
ErrHandler:
Call SendMessage(GetDesktopWindow, WM_SETREDRAW, True, 0&)
End Sub
Note that the use of error handling is even more crucial here because you don't want your whole screen to remain frozen in case of an unhandled error.
[However, if it were to happen, you could still] press the keyboard sleep key and then after entering the sleep state, press the WakeUp key .. that shoud unfreeze the computer and avoid any accidental loss of data. (source)
If the previous solution is interfering with your UI Automation process, you might want to consider a more subtle solution that solves your problem indirectly. I'm talking about minimizing the window at the beginning of your code and maximizing it at the end.
This is not exactly what you wanted, but at least the user won't see the window while the interaction occurs. Based on the tests that I've done, the fact that a window is minimized shouldn't affect UI Automation commands.
'WindowHandle declaration
#If VBA7 Then
Private WindowHandle As LongPtr
#Else
Private WindowHandle As Long
#End If
'GetForegroundWindow declaration
#If VBA7 Then
Private Declare PtrSafe Function GetForegroundWindow Lib "user32" () As LongPtr
#Else
Private Declare Function GetForegroundWindow Lib "user32" () As Long
#End If
'ShowWindow declaration
#If VBA7 Then
Private Declare PtrSafe Function ShowWindow Lib "user32" (ByVal hwnd As LongPtr, ByVal nCmdShow As Long) As Long
#Else
Private Declare Function ShowWindow Lib "user32" (ByVal hWnd As Long, ByVal nCmdShow As Long) As Long
#End If
'ShowWindow Commands
Public Const SW_HIDE = 0
Public Const SW_SHOWNORMAL = 1
Public Const SW_NORMAL = 1
Public Const SW_SHOWMINIMIZED = 2
Public Const SW_SHOWMAXIMIZED = 3
Public Const SW_MAXIMIZE = 3
Public Const SW_SHOWNOACTIVATE = 4
Public Const SW_SHOW = 5
Public Const SW_MINIMIZE = 6
Public Const SW_SHOWMINNOACTIVE = 7
Public Const SW_SHOWNA = 8
Public Const SW_RESTORE = 9
Public Const SW_SHOWDEFAULT = 10
Public Const SW_MAX = 10
Sub MinimizeAndMaximize()
On Error GoTo ErrHandler
WindowHandle = GetForegroundWindow
ShowWindow WindowHandle, SW_SHOWMINIMIZED
'Your code here
ErrHandler:
ShowWindow WindowHandle, SW_MAXIMIZE
End Sub