If you scour the internet you’ll find plenty of examples of how to give your VBA userform minimize and maximize buttons. The exact code will vary depending on the trinkets the programmer wants, but a typical snippet will look something like this:
Option Explicit Private Declare Function GetWindowLong _ Lib "user32.dll" Alias "GetWindowLongA" ( _ ByVal hwnd As Long, _ ByVal nIndex As Long) As Long Private Declare Function SetWindowLong _ Lib "user32.dll" Alias "SetWindowLongA" ( _ ByVal hwnd As Long, _ ByVal nIndex As Long, _ ByVal dwNewLong As Long) As Long Private Declare Function FindWindowA _ Lib "user32.dll" ( _ ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long Private Declare Function DrawMenuBar _ Lib "user32.dll" ( _ ByVal hwnd As Long) As Long Private Sub UserForm_Initialize() CreateMenu End Sub Private Sub CreateMenu() Const GWL_STYLE As Long = -16 Const WS_SYSMENU As Long = &H80000 Const WS_MINIMIZEBOX As Long = &H20000 Const WS_MAXIMIZEBOX As Long = &H10000 Dim lngFrmWndHdl As Long Dim lngStyle As Long lngFrmWndHdl = FindWindowA(vbNullString, Me.Caption) lngStyle = GetWindowLong(lngFrmWndHdl, GWL_STYLE) lngStyle = lngStyle Or WS_SYSMENU 'Add SystemMenu lngStyle = lngStyle Or WS_MINIMIZEBOX 'Add MinimizeBox lngStyle = lngStyle Or WS_MAXIMIZEBOX 'Add MaximizeBox SetWindowLong lngFrmWndHdl, GWL_STYLE, lngStyle DrawMenuBar lngFrmWndHdl End Sub
The code is fine but it needs to be updated to run in 64-bit environments. In short, the way do to this is to include the PtrSafe
keyword after each Declare
keyword and to look up each function on MSDN and then use LongPtr
instead of Long
for each parameter and output if it represents a handle or a pointer. If you want the functions to work across both 32-bit and 64-bit Office then you may have to declare them multiple times inside a conditional compilation block. There are a couple of useful articles on MSDN which go into this in more depth:
- Compatibility Between the 32-bit and 64-bit Versions of Office 2010
- Office Talk: Working with VBA in the 32-bit and 64-bit Versions of Office 2010
The MSDN documentation for GetWindowLong and SetWindowLong states that they’ve been superseded by GetWindowLongPtr and SetWindowLongPtr, so it sounds like a good idea to use the new ones instead:
Note To write code that is compatible with both 32-bit and 64-bit versions of Windows, use GetWindowLongPtr. When compiling for 32-bit Windows, GetWindowLongPtr is defined as a call to the GetWindowLong function.
To declare these new functions correctly in VBA, consideration needs to be given to the possibility that you’re using 32-bit Office – even if it is on 64-bit Windows. On 64-bit Windows, 32-bit Excel will try to use the 32-bit User32.dll
in the C:\Windows\SYSWOW64
directory. This makes perfect sense but if you don’t believe me you can see it by using Process Explorer:
The 32-bit User32.dll doesn’t contain the GetWindowLongPtr
and SetWindowLongPtr
functions. You can see this by using Dependency Walker:
In other words, if you try to call them from 32-bit Excel you will get the run-time error 453: Can’t Find DLL entry point [method name] in User32.dll. This means that the Win64
compilation constant needs to be used rather than VBA7
(I will talk more about these constants in a later blog post), so the API declarations should be written as follows:
Option Explicit #If Win64 Then Private Declare PtrSafe Function GetWindowLongPtr _ Lib "user32.dll" Alias "GetWindowLongPtrA" ( _ ByVal hwnd As LongPtr, _ ByVal nIndex As Long) As LongPtr Private Declare PtrSafe Function SetWindowLongPtr _ Lib "user32.dll" Alias "SetWindowLongPtrA" ( _ ByVal hwnd As LongPtr, _ ByVal nIndex As Long, _ ByVal dwNewLong As LongPtr) As LongPtr Private Declare PtrSafe Function FindWindowA _ Lib "user32.dll" ( _ ByVal lpClassName As String, _ ByVal lpWindowName As String) As LongPtr Private Declare PtrSafe Function DrawMenuBar _ Lib "user32.dll" ( _ ByVal hwnd As LongPtr) As Long #Else Private Declare Function GetWindowLongPtr _ Lib "user32.dll" Alias "GetWindowLongA" ( _ ByVal hwnd As Long, _ ByVal nIndex As Long) As Long Private Declare Function SetWindowLongPtr _ Lib "user32.dll" Alias "SetWindowLongA" ( _ ByVal hwnd As Long, _ ByVal nIndex As Long, _ ByVal dwNewLong As Long) As Long Private Declare Function FindWindowA _ Lib "user32.dll" ( _ ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long Private Declare Function DrawMenuBar _ Lib "user32.dll" ( _ ByVal hwnd As Long) As Long #End If Private Sub UserForm_Initialize() CreateMenu End Sub Private Sub CreateMenu() Const GWL_STYLE As Long = -16 Const WS_SYSMENU As Long = &H80000 Const WS_MINIMIZEBOX As Long = &H20000 Const WS_MAXIMIZEBOX As Long = &H10000 #If Win64 Then Dim lngFrmWndHdl As LongPtr Dim lngStyle As LongPtr #Else Dim lngFrmWndHdl As Long Dim lngStyle As Long #End If lngFrmWndHdl = FindWindowA(vbNullString, Me.Caption) lngStyle = GetWindowLongPtr(lngFrmWndHdl, GWL_STYLE) lngStyle = lngStyle Or WS_SYSMENU 'Add SystemMenu lngStyle = lngStyle Or WS_MINIMIZEBOX 'Add MinimizeBox lngStyle = lngStyle Or WS_MAXIMIZEBOX 'Add MaximizeBox SetWindowLongPtr lngFrmWndHdl, GWL_STYLE, lngStyle DrawMenuBar lngFrmWndHdl End Sub
Pingback: Getting A Handle On Userforms [VBA] | RAD Excel