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 would like to simply show the outcome at end. I tried using Application.ScreenUpdating, but it looks like it is a setting that does not apply to the ribbon.
Is there any setting out there that can be used to manipulate the screen updates for the Excel ribbon?
EDIT: the Automation framework can be referenced as UIAutomationClient in the VBA project. The dll file is UIAutomationCore.dll
Thanks
Summary
LockWindowUpdate: An Unreliable Solution
WM_SETREDRAW: A (too?) Powerful Solution
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
Related
There is no documentation of "windows" key :https://learn.microsoft.com/en-us/office/vba/api/excel.application.sendkeys
I wanted a combination of "Windows+UP" key to Maximize Active window.
So, I tried "Ctrl+Esc" for windows key + {UP} : Application.SendKeys ("^({ESC}{UP})") but it didn't work.
Is there a way to send windows key using API, dll etc without using external programs like AutoIt.
Is there a way to send windows key using API, dll etc without using external programs like AutoIt
Yes, you can use FindWindow and ShowWindow API to maximize a window. This is more reliable than using Sendkeys.
Here is an example
Option Explicit
Private Declare PtrSafe Function ShowWindow Lib "user32" _
(ByVal hwnd As LongPtr, ByVal nCmdSHow As Long) As Long
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Private Const SW_SHOWMAXIMIZED = 3
Sub MaximizeAWindow()
Dim hwnd As Long
Dim Caption As String
Caption = "THIS IS THE CAPTION OF THE WINDOW"
hwnd = FindWindow(vbNullString, Caption)
If hwnd <> 0 Then
ShowWindow hwnd, SW_SHOWMAXIMIZED
Else
MsgBox "Unable to find the window. Is the caption correct?"
End If
End Sub
You may be also interested in GetForegroundWindow and the GetWindowText API to get the caption of the current active window?
I'm using Excel 2016 (Office Theme:Colorful) and unfortunately when I write some code with a user defined text for displaying in status bar, the status bar changes its background color to dark green instead of remaining in vbButtonFace (&H8000000F). The result is an unreadable status bar text message, considered that the font color remains dark grey as expected.
I know it directly can't be done by VBA (please, don't suggest to me of changing Office theme... it's not an option!)
Googling around I found some code which uses API functions SendMessage and GetSysColor calls that I rearranged as follow:
#If VBA7 Then
Public 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
Public Declare PtrSafe Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
#Else
Public Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Public Declare Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
#End If
Private Const CCM_FIRST As Long = &H2000 'Common Control Messages
Private Const CCM_SETBKCOLOR As Long = (CCM_FIRST + 1)
Private Const PBM_SETBKCOLOR As Long = CCM_SETBKCOLOR 'Progress Bar Messages
Private Const COLOR_BTNFACE = &H8000000F
#If VBA7 Then
Public Sub SetStatusBackColour(hwndStatBar As LongPtr, ByVal clrref As Long)
Call SendMessage(hwndStatBar, PBM_SETBKCOLOR, 0&, ByVal clrref)
End Sub
#Else
Public Sub SetStatusBackColour(hwndStatBar As Long, ByVal clrref As Long)
Call SendMessage(hwndStatBar, PBM_SETBKCOLOR, 0&, ByVal clrref)
End Sub
#End If
Public Function EvalCol(ByVal inCol As Long) As Long ' Returns the RGB of a long colour value (System colour aware)
If ((inCol And &HFFFFFF00) = &H80000000) Then EvalCol = GetSysColor(inCol And &HFF) Else EvalCol = inCol
End Function
Private Sub Test()
Call SetStatusBackColour(StatusBar1.hwnd, EvalCol(vbButtonFace))
'Call SetStatusBackColour(StatusBar1.hwnd, COLOR_BTNFACE) 'without GetSysColor API function call
End Sub
Now the problem is... How can I find the hwnd of the Excel Status Bar?
Obviously, if this approach doesn't apply anymore or a different approach can be used instead, please tell me!
You may work around this issue by updating screen before setting the value of status bar then turning it back to False.
For example:
Application.ScreenUpdating = True
Application.StatusBar = "Transferring Records: " & I & " of " & X & " completed..."
Application.ScreenUpdating = False
I have exactly the same problem of the question Excel Chart_MouseUp event not trapped but there no answer has been given
I'm interested in the sequence press shift-ctrl-left mouse (MouseDown event is fired), move left mouse (MouseMove event is fired), release shift-ctrl-left mouse (MouseUp event is NOT fired)
How is it possible that a MouseDown event is fired and the corresponding MouseUp event Not (under the same circumstances)!!!
How can I solve the issue?
Thanks in advance for the help you can give me
Even if no answer I received here about this topic, hoping this could be usefull for others, I append a solution given to me by Peter Thornton, which I thank a lot for the suggestions given.
There is a different way, not sure why I didn’t think of it before --> check the state of the left mouse button with a timer
Below my code written for purpose
Private Declare PtrSafe Function SetTimer Lib "User32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Private Declare PtrSafe Function KillTimer Lib "User32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
Private Declare PtrSafe Function GetAsyncKeyState Lib "User32" (ByVal vKey As Long) As Integer
Private Declare PtrSafe Function GetSystemMetrics32 Lib "User32" Alias "GetSystemMetrics" (ByVal nIndex As Long) As Long
Private Const HWND_ZERO As LongPtr = 0
Private Const TIMER_MILLISECS = 150&
Private Const VK_LBUTTON = &H1 'Left mouse button
Private Const VK_RBUTTON = &H2 'Right mouse button
Private Const SM_SWAPBUTTON = 23& 'Left and Right mouse buttons are logically swapped (Left mouse button is the default Primary button)
Private mTimerID As LongPtr
Public Sub StartTimer()
' call StartTimer in the chart's MouseDown event
If mTimerID Then
Call EndTimer
End If
mTimerID = SetTimer(HWND_ZERO, mTimerID, TIMER_MILLISECS, AddressOf TimerProcedure)
End Sub
Private Function TimerProcedure() As LongPtr
Dim Ch As Chart
Dim obj As Variant
Dim ret As Integer
On Error Resume Next
If mTimerID = 0 Then
Call EndTimer
Else
'map the mouse logical Primary Button to the mouse phisical Left or Right Buttons
If Not GetSystemMetrics32(SM_SWAPBUTTON) Then ret = GetAsyncKeyState(VK_LBUTTON) Else ret = GetAsyncKeyState(VK_RBUTTON)
'the condition is satisfied if the mouse Left Button state is correctly trapped and the Left Button is released
If Not ret And 32768 Then
Call EndTimer
mTimerID = 0
Call UserDefinedChartMouseUp() procedure
End If
End If
End Function
Private Sub EndTimer()
On Error Resume Next
Call KillTimer(HWND_ZERO, mTimerID)
End Sub
Please Look at the code below and test it:
Private Sub CommandButton1_Click()
MsgBox "This window converted Right to Left!", vbMsgBoxRtlReading
End Sub
This code convert the message window from right to left. As the close button moves to the left of the window. How do I do this for userforms?
(Hope T.M., Mathieu Guindon and ... does not say: "Your question is amiss. Please read the links ....")
Like the picture below (Of course photo is photoshop!):
Simulate Right To Left display as in MsgBox
It'll be necessary to use some API *) functions to get the wanted layout independant from language settings using right to left functionality by default.
Identify the Userform's handle to get access to further API methods
Remove the Userform's title bar
Replace it e.g. with a Label control displaying the caption and give it drag functionality to move the UserForm (here: Label1).
Use another control (here: Label2) to simulate the system escape "x".
*) API - Application Programming Interface
A simple UserForm code example
All you need is to provide for 2 Label controls where Label1 replaces the title bar and receives the UserForm's caption and Label2 simulates the system Escape "x". Furthermore this example uses a Type declaration for easy disposal of the UserForm handle for several event procedures needing it for further API actions.
► Note to 2nd edit as of 10/22 2018
As a window handle is declared as LongPtr in Office 2010 or higher and as Long in versions before, it was necessary to differentiate between the different versions by conditional compile constants (e.g. #If VBA7 Then ... #Else ... #End If; cf. section II. using also the Win64 constant to identify actually installed 64bit Office systems - note that frequently Office is installed as 32bit by default).
Option Explicit ' declaration head of userform code module
#If VBA7 Then ' compile constant for Office 2010 and higher
Private Type TThis ' Type declaratation
frmHandle As LongPtr ' receives form window handle 64bit to identify this userform
End Type
#Else ' older versions
Private Type TThis ' Type declaratation
frmHandle As Long ' receives form window handle 32bit to identify this userform
End Type
#End If
Dim this As TThis ' this - used by all procedures within this module
Private Sub UserForm_Initialize()
' ~~~~~~~~~~~~~~~~~~~~~~~
' [1] get Form Handle
' ~~~~~~~~~~~~~~~~~~~~~~~
this.frmHandle = Identify(Me) ' get UserForm handle via API call (Long)
' ~~~~~~~~~~~~~~~~~~~~~~~
' [2] remove System Title Bar
' ~~~~~~~~~~~~~~~~~~~~~~~
HideTitleBar (this.frmHandle) ' hide title bar via API call
End Sub
Private Sub Label1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
' Purpose: Replaces System Title Bar (after removal via API) and receives dragging functionality
' ~~~~~~~~~~~~~~~~~~~~~~~~~~
' [3] allow to move UserForm
' ~~~~~~~~~~~~~~~~~~~~~~~~~~
If Button = 1 Then DragForm this.frmHandle
End Sub
Private Sub Label2_Click()
' Purpose: Label "x" replaces System Escape (after removal in step [2])and hides UserForm
' ~~~~~~~~~~~~~~~~~
' [4] hide UserForm
' ~~~~~~~~~~~~~~~~~
Me.Hide
End Sub
Private Sub UserForm_Layout()
Me.RightToLeft = True
' Simulated Escape Icon
Me.Label2.Caption = " x"
Me.Label2.BackColor = vbWhite
Me.Label2.Top = 0
Me.Label2.Left = 0
Me.Label2.Width = 18: Me.Label2.Height = 18
' Simulated UserForm Caption
Me.Label1.Caption = Me.Caption
Me.Label1.TextAlign = fmTextAlignRight ' <~~ assign right to left property
Me.Label1.BackColor = vbWhite
Me.Label1.Top = 0: Me.Label1.Left = Me.Label2.Width: Me.Label1.Height = Me.Label2.Height
Me.Label1.Width = Me.Width - Me.Label2.Width - 4
End Sub
II. Separate code module for API functions
a) Declaration head with constants and special API declarations
It's necessary to provide for different application versions as the code declarations differ in some arguments (e.g. PtrSafe). 64 bit declarations start as follows: Private Declare PtrSafe ...
Take also care of the correct declarations via #If, #Else and #End If allowing version dependant compilation.
The prefix &H used in constants stands for hexadecimal values.
Option Explicit
Private Const WM_NCLBUTTONDOWN = &HA1&
Private Const HTCAPTION = 2&
Private Const GWL_STYLE = (-16)
Private Const WS_BORDER = &H800000
Private Const WS_DLGFRAME = &H400000
Private Const WS_CAPTION = WS_BORDER Or WS_DLGFRAME
#If VBA7 Then ' True if you're using Office 2010 or higher
' [0] ReleaseCapture
Private Declare PtrSafe Sub ReleaseCapture Lib "User32" ()
' [1] SendMessage
Private 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 ' << arg's hWnd, wParam + function type: LongPtr
' [2] FindWindow
Private Declare PtrSafe Function FindWindow Lib "User32" _
Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As LongPtr ' << function type: LongPtr
' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Two API functions requiring the Win64 compile constant for 64bit Office installations
' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#If Win64 Then ' true if Office explicitly installed as 64bit
' [3a] Note that GetWindowLong has been replaced by GetWindowLongPtr
Private Declare PtrSafe Function GetWindowLongPtr Lib "User32" _
Alias "GetWindowLongPtrA" _
(ByVal hWnd As LongPtr, _
ByVal nIndex As Long) As LongPtr
' [3b] Note that GetWindowLong has been replaced by GetWindowLongPtr
' Changes an attribute of the specified window.
' The function also sets a value at the specified offset in the extra window memory.
Private Declare PtrSafe Function SetWindowLongPtr Lib "User32" _
Alias "SetWindowLongPtrA" _
(ByVal hWnd As LongPtr, _
ByVal nIndex As Long, _
ByVal dwNewLong As LongPtr) As LongPtr
#Else ' true if Office install defaults 32bit
' [3aa] Note that GetWindowLong has been replaced by GetWindowLongPtr Alias GetWindowLongA !
Private Declare PtrSafe Function GetWindowLongPtr Lib "User32" _
Alias "GetWindowLongA" _
(ByVal hWnd As LongPtr, _
ByVal nIndex As Long) As LongPtr
' [3bb] Note that GetWindowLong has been replaced by GetWindowLongPtr Alias SetWindowLongA !
Private Declare PtrSafe Function SetWindowLongPtr Lib "User32" _
Alias "SetWindowLongA" _
(ByVal hWnd As LongPtr, _
ByVal nIndex As Long, _
ByVal dwNewLong As LongPtr) As LongPtr
#End If
' [4] DrawMenuBar
Private Declare PtrSafe Function DrawMenuBar Lib "User32" _
(ByVal hWnd As LongPtr) As Long ' << arg hWnd: LongPtr
#Else ' True if you're using Office before 2010 ('97)
Private Declare Sub ReleaseCapture Lib "User32" ()
Private Declare Function SendMessage Lib "User32" _
Alias "SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function FindWindow Lib "User32" _
Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Declare Function GetWindowLong Lib "User32" _
Alias "GetWindowLongA" _
(ByVal hWnd As Long, _
ByVal nIndex As Long) As Long
Private Declare Function SetWindowLong Lib "User32" _
Alias "SetWindowLongA" _
(ByVal hWnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long
Private Declare Function DrawMenuBar Lib "User32" _
(ByVal hWnd As Long) As Long
#End If
b) Following Procedures (after section a)
' ~~~~~~~~~~~~~~~~~~~~~~
' 3 Procedures using API
' ~~~~~~~~~~~~~~~~~~~~~~
#If VBA7 Then ' Office 2010 and higher
Public Function Identify(frm As Object) As LongPtr
' Purpose: [1] return window handle of form
' Note: vbNullString instead of ThunderXFrame (97) and class names of later versions
Identify = FindWindow(vbNullString, frm.Caption)
End Function
Public Sub HideTitleBar(hWnd As LongPtr)
' Purpose: [2] remove Userform title bar
SetWindowLongPtr hWnd, GWL_STYLE, GetWindowLongPtr(hWnd, GWL_STYLE) And Not WS_CAPTION
End Sub
Public Sub ShowTitleBar(hWnd As LongPtr)
' Purpose: show Userform title bar
SetWindowLongPtr hWnd, GWL_STYLE, GetWindowLongPtr(hWnd, GWL_STYLE) Or WS_CAPTION
End Sub
Public Sub DragForm(hWnd As LongPtr)
' Purpose: [3] allow to drag & move userform via control (here via e.g.: Label1)
Call ReleaseCapture
Call SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0&)
End Sub
#Else ' vers. before Office 2010 (Office '97)
Public Function Identify(frm As Object) As Long
' Purpose: [1] return window handle of form
' Note: vbNullString instead of ThunderXFrame (97) and class names of later versions
Identify = FindWindow(vbNullString, frm.Caption)
End Function
Public Sub HideTitleBar(hWnd As Long)
' Purpose: [2] remove Userform title bar
SetWindowLong hWnd, GWL_STYLE, GetWindowLong(hWnd, GWL_STYLE) And Not WS_CAPTION
End Sub
' Public Sub ShowTitleBar(HWND As Long)
' ' Purpose: show Userform title bar
' SetWindowLong HWND, GWL_STYLE, GetWindowLong(HWND, GWL_STYLE) Or WS_CAPTION
' End Sub
Public Sub DragForm(hWnd As Long)
' Purpose: [3] allow to drag & move userform via control (here via e.g.: Label1)
Call ReleaseCapture
Call SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0&)
End Sub
#End If
► Caveat: API declarations not tested for actually installed 64 bit systems in Office 2010 or higher. The 2nd Edit as of 10/22 2018 tries to correct several LongPtr declarations (only for pointers to a → handle or → memory location) and using the current Get/SetWindowLongPtr function differentiating explicitly between Win64 and Win32; cf. also edited Type declaration in the UserForm code module's declaration head).
See also Compatibility between 32bit and 64bit Versions of Office 2010 and Office 2010 Help Files: Win32API PtrSafe with 64bit Support
Additional note
UserForms are Windows and can be identified by their window handle.
The API function used for this purpose is FindWindow disposing of two arguments:
1) A string giving the name of the class of the window it needs to find and 2) a string giving the caption of the window (UserForm) it needs to find.
Therefore frequently one distinguishes between version '97 (UserForm class name "ThunderXFrame") and later versions ("ThunderDFrame"):
If Val(Application.Version) < 9 Then
hWnd = FindWindow("ThunderXFrame", frm.Caption) ' if used within Form: Me.Caption
Else ' later versions
hWnd = FindWindow("ThunderDFrame", frm.Caption) ' if used within Form: Me.Caption
End If
However using vbNullString (and unique captions!) instead makes coding much easier:
hWnd = FindWindow(vbNullString, frm.Caption) ' if used within Form: Me.Caption
Recommended further reading
UserForm code modules actually are classes and should be used as such. So I recommend reading M. Guindon's article UserForm1.Show. - Possibly of some interest, as well is Destroy a modeless UserForm instance properly
I have an Excel VBA UDF that performs some expensive calculations. Currently, Excel tries to run the function when the user clicks on the Insert Function dialog (the 'fx' button next to the formula bar), and this causes problems in my code.
Is there a way I can set the function to not calculate when the user has the Insert Function dialog (or the Function Arguments dialog, which is what shows up when the function name is already provided) open? I'd like to have the function only run when the user enters the formula in a cell or refreshes the sheet.
try adding this code to the start of your function:
If (Not Application.CommandBars("Standard").Controls(1).Enabled) Then Exit Function
It will quit your UDF if the function wizard is being used
There is one circumstance in which the "CommandBars" solution provided by Charles Williams fails, falsely indicating that the function wizard is active when it's not.
It happens when you open a comma-separated text file in Excel in which case all open Excel workbooks are recalculated, even if Excel calculation is set to manual. That's quite disruptive if you have workbooks open with slow-to-calculate VBA UDFs that use the CommandBars test to exit early if the Wizard is thought to be active.
Charles further suggests that the Windows API can be used as an alternative approach. I've not been able to find such code elsewhere so here's my attempt to implement Charles' suggestion.
Tested only on English-language 64-bit Excel 365.
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare PtrSafe Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function GetWindow Lib "user32" (ByVal hwnd As LongPtr, ByVal wCmd As Long) As Long
Private Declare PtrSafe Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As LongPtr, ByRef lpdwProcessId As Long) As Long
Private Declare PtrSafe Function GetCurrentProcessId Lib "kernel32" () As Long
Private Const GW_HWNDNEXT = 2
Function FunctionWizardActive() As Boolean
Dim ExcelPID As Long
Dim lhWndP As LongPtr
Dim WindowTitle As String
Dim WindowPID As Long
Const FunctionWizardCaption = "Function Arguments" 'This won't work for non English-language Excel
If TypeName(Application.Caller) = "Range" Then
'The "CommandBars test" below is usually sufficient to determine that the Function Wizard is active,
'but can sometimes give a false positive. Example: When a csv file is opened (via File Open) then all
'active workbooks are calculated (even if calculation is set to manual!) with
'Application.CommandBars("Standard").Controls(1).Enabled being False
'So apply a further test using Windows API to loop over all windows checking for a window with title "Function Arguments", checking also the process id.
If Not Application.CommandBars("Standard").Controls(1).Enabled Then
ExcelPID = GetCurrentProcessId()
lhWndP = FindWindow(vbNullString, vbNullString) 'PARENT WINDOW
Do While lhWndP <> 0
WindowTitle = String(GetWindowTextLength(lhWndP) + 1, Chr$(0))
GetWindowText lhWndP, WindowTitle, Len(WindowTitle)
WindowTitle = Left$(WindowTitle, Len(WindowTitle) - 1)
If WindowTitle = FunctionWizardCaption Then
GetWindowThreadProcessId lhWndP, WindowPID
If WindowPID = ExcelPID Then
FunctionWizardActive = True
Exit Function
End If
End If
lhWndP = GetWindow(lhWndP, GW_HWNDNEXT)
Loop
End If
End If
End Function
With that function available, you can amend your slow VBA UDFs with the code:
If FunctionWizardActive() Then Exit Function