VBA: Application.ScreenUpdating equivalent for actions on the Excel ribbon

前端 未结 1 1857
独厮守ぢ
独厮守ぢ 2021-01-24 03:00

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

相关标签:
1条回答
  • 2021-01-24 03:23

    Summary

    1. LockWindowUpdate: An Unreliable Solution
    2. WM_SETREDRAW: A (too?) Powerful Solution
    3. SW_SHOWMINIMIZED: A Subtle Solution

    1) LockWindowUpdate: An Unreliable Solution

    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:

    • Windows API SendMessage to freeze screen in Excel VBA (Doesn't work on all machines)
    • API LockWindowUpdate don't work as expected - You can read in that second post the following:

      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
    

    2) WM_SETREDRAW: A Powerful Solution

    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.

    A more drastic alternative

    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)

    3) SW_SHOWMINIMIZED: A Subtle Solution

    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
    
    0 讨论(0)
提交回复
热议问题