Jaafar Tribak
Well-known Member
- Joined
- Dec 5, 2002
- Messages
- 9,797
- Office Version
- 2016
- Platform
- Windows
Hi,
This is a vba code that hopefully accomplishes what the title of the thread says. Obviously, the forms must be modeless so that the user can freely move from one form to aanother. Problem is, using modeless forms makes this task difficult as we cannot subclass modeless forms to properly intercept and handle the mouse_move messages. Only modal forms can be subclassed. Same problem with mouse Window hooks.
So I decided to experiment with a windows timer to monitor the moving of the userforms and this is what I have come to.
Basically, each form is programmatically assigned a checkbox button on the titlebar next to the form's close X button (see preview). This button will take charge of toggling the Grouping state of the form.
I wrote some code some time ago for grouping forms but it was very limited compared to this one.
UserFormsGrouping.xlsm
The sluggish movement that appears in the following preview is due to the screen recording software. The actual project is much smoother.
1- API code in a Standard Module:
2- Code Usage in the UserForm module:
Tested on (Win7 Pro x32bit Excel 2007) and on (Win10 x64bit Excel 2016 x64bit)
Regards.
This is a vba code that hopefully accomplishes what the title of the thread says. Obviously, the forms must be modeless so that the user can freely move from one form to aanother. Problem is, using modeless forms makes this task difficult as we cannot subclass modeless forms to properly intercept and handle the mouse_move messages. Only modal forms can be subclassed. Same problem with mouse Window hooks.
So I decided to experiment with a windows timer to monitor the moving of the userforms and this is what I have come to.
Basically, each form is programmatically assigned a checkbox button on the titlebar next to the form's close X button (see preview). This button will take charge of toggling the Grouping state of the form.
I wrote some code some time ago for grouping forms but it was very limited compared to this one.
UserFormsGrouping.xlsm
The sluggish movement that appears in the following preview is due to the screen recording software. The actual project is much smoother.
1- API code in a Standard Module:
VBA Code:
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 MSG
#If Win64 Then
hWnd As LongLong
message As Long
wParam As LongLong
lParam As LongLong
#Else
hWnd As Long
message As Long
wParam As Long
lParam As Long
#End If
time As Long
pt As POINTAPI
End Type
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
#If VBA7 Then
#If Win64 Then
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongLong)
Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal Point As LongLong) As LongLong
Private Declare PtrSafe Function PtInRect Lib "user32" (lpRect As RECT, ByVal arg2 As LongPtr) As Long
Private Declare PtrSafe Function SetWindowLong Lib "user32" Alias "SetWindowLongPtrA" (ByVal hWnd As LongLong, ByVal nIndex As Long, ByVal dwNewLong As LongLong) As LongLong
Private Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongPtrA" (ByVal hWnd As LongLong, ByVal nIndex As Long) As LongLong
#Else
Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
Private Declare PtrSafe Function PtInRect Lib "user32" (lpRect As RECT, ByVal arg1 As Long, ByVal arg2 As Long) As Long
Private Declare PtrSafe Function SetWindowLong Lib "user32.dll" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare PtrSafe Function GetWindowLong Lib "user32.dll" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
#End If
Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUF As LongPtr) As Long
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 CreateWindowEx Lib "user32" Alias "CreateWindowExW" (ByVal dwExStyle As Long, ByVal lpClassName As LongPtr, ByVal lpWindowName As LongPtr, ByVal dwStyle As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hWndParent As LongPtr, ByVal hMenu As LongPtr, ByVal hInstance As LongPtr, lpParam As Any) As LongPtr
Private Declare PtrSafe Function DestroyWindow Lib "user32" (ByVal hWnd As LongPtr) As Long
Private Declare PtrSafe Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As LongPtr
Private Declare PtrSafe Function IsWindowEnabled Lib "user32" (ByVal hWnd As LongPtr) As Long
Private Declare PtrSafe Function EnableWindow Lib "user32" (ByVal hWnd As LongPtr, ByVal fEnable As Long) As Long
Private Declare PtrSafe Function GetFocus Lib "user32" () As LongPtr
Private Declare PtrSafe Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hWnd As LongPtr, ByVal wFlag As Long) As LongPtr
Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr
Private Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hWnd As LongPtr) As Long
Private Declare PtrSafe Function SetActiveWindow Lib "user32" (ByVal hWnd As LongPtr) As LongPtr
Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
Private Declare PtrSafe Function GetWindowRect Lib "user32" (ByVal hWnd As LongPtr, lpRect As RECT) As Long
Private Declare PtrSafe Function SetRect Lib "user32" (lpRect As RECT, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
Private Declare PtrSafe Function IntersectRect Lib "user32" (lpDestRect As RECT, lpSrc1Rect As RECT, lpSrc2Rect As RECT) As Long
Private Declare PtrSafe Function SetWindowPos Lib "user32" (ByVal hWnd As LongPtr, ByVal hWndInsertAfter As LongPtr, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As LongPtr
Private Declare PtrSafe Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Private Declare PtrSafe Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Private Declare PtrSafe Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
Private Declare PtrSafe Function SetLayeredWindowAttributes Lib "user32" (ByVal hWnd As LongPtr, ByVal crey As Long, ByVal bAlpha As Byte, ByVal dwFlags As Long) As LongPtr
Private Declare PtrSafe Function SystemParametersInfo Lib "user32" Alias "SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam As Long, ByRef lpvParam As Any, ByVal fuWinIni As Long) As Long
Private Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
Private Declare PtrSafe Function PeekMessage Lib "user32" Alias "PeekMessageA" (lpMsg As MSG, ByVal hWnd As LongPtr, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long
Private Declare PtrSafe Function WaitMessage Lib "user32" () As Long
Private Declare PtrSafe Function SetProp Lib "user32" Alias "SetPropA" (ByVal hWnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long
Private Declare PtrSafe Function GetProp Lib "user32" Alias "GetPropA" (ByVal hWnd As LongPtr, ByVal lpString As String) As LongPtr
Private Declare PtrSafe Function DrawText Lib "user32" Alias "DrawTextW" (ByVal hDC As LongPtr, ByVal lpStr As LongPtr, ByVal nCount As Long, lpRect As RECT, ByVal wFormat As Long) As Long
Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hWnd As LongPtr) As LongPtr
Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hWnd As LongPtr, ByVal hDC As LongPtr) As Long
Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, ByVal lpiid As LongPtr) As LongPtr
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "OLEACC.DLL" (ByVal hWnd As LongPtr, ByVal dwId As Long, ByVal riid As LongPtr, ppvObject As Any) As Long
Private Declare PtrSafe Function AccessibleChildren Lib "Oleacc" (ByVal paccContainer As Office.IAccessible, ByVal iChildStart As Long, ByVal cChildren As Long, ByRef rgvarChildren As Any, ByRef pcObtained As Long) As Long
Private Declare PtrSafe Function DwmIsCompositionEnabled Lib "dwmapi" (ByRef pfEnabled As Long) As Long
Private Declare PtrSafe Function TranslateColor Lib "oleAut32.dll" Alias "OleTranslateColor" (ByVal Clr As OLE_COLOR, ByVal palet As Long, col As Long) As Long
#Else
Private Declare Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
Private Declare Function PtInRect Lib "user32" (lpRect As RECT, ByVal arg1 As Long, ByVal arg2 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 GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Private Declare Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUf As Long) As Long
Private Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Private Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
Private Declare Function CreateWindowEx Lib "user32" Alias "CreateWindowExW" (ByVal dwExStyle As Long, ByVal lpClassName As Long, ByVal lpWindowName As Long, ByVal dwStyle As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hWndParent As Long, ByVal hMenu As Long, ByVal hInstance As Long, lpParam As Any) As Long
Private Declare Function DestroyWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
Private Declare Function IsWindowEnabled Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function EnableWindow Lib "user32" (ByVal hwnd As Long, ByVal fEnable As Long) As Long
Private Declare Function GetFocus Lib "user32" () As Long
Private Declare Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As Long, ByVal wFlag As Long) As Long
Private Declare Function GetActiveWindow Lib "user32" () As Long
Private Declare Function IsWindowVisible Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function SetActiveWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
Private Declare Function SetRect Lib "user32" (lpRect As RECT, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
Private Declare Function IntersectRect Lib "user32" (lpDestRect As RECT, lpSrc1Rect As RECT, lpSrc2Rect As RECT) As Long
Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Private Declare Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
Private Declare Function SetLayeredWindowAttributes Lib "user32" (ByVal hwnd As Long, ByVal crey As Long, ByVal bAlpha As Byte, ByVal dwFlags As Long) As Long
Private Declare Function SystemParametersInfo Lib "user32" Alias "SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam As Long, ByRef lpvParam As Any, ByVal fuWinIni As Long) As Long
Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
Private Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" (lpMsg As MSG, ByVal hwnd As Long, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long
Private Declare Function WaitMessage Lib "user32" () As Long
Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
Private Declare Function DrawText Lib "user32" Alias "DrawTextW" (ByVal hDC As Long, ByVal lpStr As Long, ByVal nCount As Long, lpRect As RECT, ByVal wFormat As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hDC As Long) As Long
Private Declare Function IIDFromString Lib "ole32.dll" (ByVal lpsz As Long, ByVal lpiid As Long) As Long
Private Declare Function AccessibleObjectFromWindow Lib "OLEACC.DLL" (ByVal hwnd As Long, ByVal dwId As Long, ByVal riid As Long, ppvObject As Any) As Long
Private Declare Function AccessibleChildren Lib "Oleacc" (ByVal paccContainer As Office.IAccessible, ByVal iChildStart As Long, ByVal cChildren As Long, ByRef rgvarChildren As Any, ByRef pcObtained As Long) As Long
Private Declare Function DwmIsCompositionEnabled Lib "dwmapi" (ByRef pfEnabled As Long) As Long
Private Declare Function TranslateColor Lib "oleAut32.dll" Alias "OleTranslateColor" (ByVal Clr As OLE_COLOR, ByVal palet As Long, col As Long) As Long
#End If
Private oColl As Collection
Public Sub AddFormToGroup(ByVal UF As UserForm)
Const GWL_HWNDPARENT = (-8)
Const GWL_USERDATA = (-21)
Const WS_EX_LAYERED = &H80000
Const WS_EX_NOACTIVATE = &H8000000
Const LWA_COLORKEY = &H1
Const COLOR_WINDOW = 5
Const COLOR_BTNFACE = 15
Const WS_VISIBLE = &H10000000
Const WS_POPUP = &H80000000
Const WS_BORDER = &H800000
Const BS_AUTOCHECKBOX = &H3&
Const BS_RIGHTBUTTON = &H20
Const BS_VCENTER As Long = &HC00
#If Win64 Then
Dim hWnd As LongLong, hButton As LongLong
#Else
Dim hWnd As Long, hButton As Long
#End If
Dim tFormRect As RECT
Dim lWinStyle As Long, lWinExStyle As Long, lNewColor As Long
Call IUnknown_GetWindow(UF, VarPtr(hWnd))
lWinStyle = WS_POPUP + WS_VISIBLE + BS_RIGHTBUTTON + BS_VCENTER + BS_AUTOCHECKBOX + WS_BORDER
lWinExStyle = WS_EX_NOACTIVATE + IIf(IsAeroEnabled, WS_EX_LAYERED, 0)
hButton = CreateWindowEx(lWinExStyle, StrPtr("BUTTON"), StrPtr(ChrW(9724) & "GRP"), _
lWinStyle, 0&, 0&, 0&, 0&, 0&, 0&, GetModuleHandle(vbNullString), ByVal 0&)
Call TranslateColor(GetSysColor(COLOR_BTNFACE), 0, lNewColor)
Call SetLayeredWindowAttributes(hButton, lNewColor, 0, LWA_COLORKEY)
Call SetWindowLong(hButton, GWL_HWNDPARENT, hWnd)
Call SetWindowLong(hWnd, GWL_USERDATA, hButton)
Call SetWindowLong(hButton, GWL_USERDATA, hWnd)
Call GetWindowRect(hWnd, tFormRect)
Call SetProp(hWnd, "LEFT", tFormRect.Left)
Call SetProp(hWnd, "TOP", tFormRect.Top)
If oColl Is Nothing Then
Set oColl = New Collection
End If
oColl.Add hButton
Call ShowCheckButton(UF)
End Sub
'____________________________________________ Private Routines ___________________________________________
Private Sub ShowCheckButton(ByVal UF As UserForm)
Call SetCheckButtonsPos(UF)
MonitoringMovingOfUserForms = True
End Sub
Private Sub SetFormsPos()
Const BM_GETCHECK = &HF0
Const BST_CHECKED = &H1
Const SWP_SHOWWINDOW = &H40
Const SWP_NOACTIVATE = &H10
Const SWP_NOSIZE = &H1
Const SWP_NOZORDER = &H4
Const GWL_USERDATA = (-21)
#If Win64 Then
Dim hForm As LongLong
Dim lCheckBoxState1 As LongLong, lCheckBoxState2 As LongLong
#Else
Dim hForm As Long
Dim lCheckBoxState1 As Long, lCheckBoxState2 As Long
#End If
Static tPrevCurPos As POINTAPI
Dim oForm As UserForm
Dim lMouseOffsetX As Long
Dim lMouseOffsetY As Long
Dim sBuff As String * 256, lRet As Long
Dim tRect As RECT, tCurPos As POINTAPI
MonitoringMovingOfUserForms = False
If IsCaptionClick Then
Call SaveInitPos
End If
If FormsOffScreen Then
Call RestoreInitPos
End If
If Not IsMouseOverTitleBar(GetActiveWindow) Then
If GetAsyncKeyState(VBA.vbKeyLButton) Then
GoTo NextTimerCycle
End If
End If
If IsAltPlusF4 Then
Call SetActiveWindow(GetWindowLong(GetFocus, GWL_USERDATA))
End If
If VBA.UserForms.Count = 0 Then
Call Cleanup(tPrevCurPos)
Exit Sub
End If
Call GetCursorPos(tCurPos)
With tPrevCurPos
lMouseOffsetX = tCurPos.x - .x
lMouseOffsetY = tCurPos.y - .y
End With
For Each oForm In VBA.UserForms
Call IUnknown_GetWindow(oForm, VarPtr(hForm))
If IsWindowEnabled(Application.hWnd) = 0 Then
Call EnableWindow(GetWindowLong(hForm, GWL_USERDATA), 0)
Else
Call EnableWindow(GetWindowLong(hForm, GWL_USERDATA), 1)
End If
Call SetCheckButtonsPos(oForm)
If GetAsyncKeyState(VBA.vbKeyLButton) Then
If GetActiveWindow <> hForm Then
lRet = GetClassName(hForm, sBuff, 256)
If Left(sBuff, lRet) Like "Thunder?Frame" Then
If IsWindowEnabled(Application.hWnd) Then
lCheckBoxState1 = SendMessage(GetWindowLong(hForm, GWL_USERDATA), BM_GETCHECK, 0, 0)
lCheckBoxState2 = SendMessage(GetWindowLong(GetActiveWindow, GWL_USERDATA), BM_GETCHECK, 0, 0)
If lCheckBoxState1 = BST_CHECKED And lCheckBoxState2 = BST_CHECKED Then
If GetWindowLong(hForm, GWL_USERDATA) Then
Call GetWindowRect(hForm, tRect)
With tRect
Call SetWindowPos(hForm, 0&, .Left + lMouseOffsetX, .Top + lMouseOffsetY, _
0&, 0&, SWP_NOACTIVATE + SWP_SHOWWINDOW + SWP_NOZORDER + SWP_NOSIZE)
End With
End If
End If
End If
End If
End If
End If
Next oForm
tPrevCurPos = tCurPos
NextTimerCycle:
MonitoringMovingOfUserForms = True
End Sub
Private Sub SetCheckButtonsPos(ByVal ParentUserForm As UserForm)
Const GWL_USERDATA = (-21)
Const WM_NCACTIVATE = &H86
Const HWND_NOTOPMOST = -2
Const SWP_SHOWWINDOW = &H40
Const SWP_NOACTIVATE = &H10
Const DT_CALCRECT = &H400
#If Win64 Then
Dim hWnd As LongLong, hDC As LongLong
#Else
Dim hWnd As Long, hDC As Long
#End If
Dim tCloseButtonRect As RECT, tTextRect As RECT
Call IUnknown_GetWindow(ParentUserForm, VarPtr(hWnd))
Call KillTimer(GetWindowLong(hWnd, GWL_USERDATA), 0)
If IsWindowVisible(hWnd) Then
tCloseButtonRect = GetCloseButtonRect(ParentUserForm)
hDC = GetDC(GetWindowLong(hWnd, GWL_USERDATA))
Call DrawText(hDC, StrPtr(ChrW(9724) & "GRP"), Len(ChrW(9724) & "GRP"), tTextRect, DT_CALCRECT)
Call ReleaseDC(GetWindowLong(hWnd, GWL_USERDATA), hDC)
With tTextRect
Call SetWindowPos(GetWindowLong(hWnd, GWL_USERDATA), HWND_NOTOPMOST, _
tCloseButtonRect.Left - 1.6 * (.Right - .Left), tCloseButtonRect.Top + 2, _
1.6 * (.Right - .Left), tCloseButtonRect.Bottom - tCloseButtonRect.Top - 4, _
SWP_NOACTIVATE + SWP_SHOWWINDOW)
End With
End If
If GetFocus = GetWindowLong(hWnd, GWL_USERDATA) Then
Call SendMessage(hWnd, WM_NCACTIVATE, True, 0)
End If
End Sub
Private Property Let MonitoringMovingOfUserForms(ByVal bMonitor As Boolean)
If bMonitor Then
Call KillTimer(Application.hWnd, 0)
Call SetTimer(Application.hWnd, 0, 0, AddressOf SetFormsPos)
Else
Call KillTimer(Application.hWnd, 0)
End If
End Property
#If Win64 Then
Private Function IsMouseOverTitleBar(ByVal hWnd As LongLong) As Boolean
#Else
Private Function IsMouseOverTitleBar(ByVal hWnd As Long) As Boolean
#End If
Const SM_CYCAPTION = 4
Const SM_CYBORDER = 6
Const SM_CYDLGFRAME = 8
Const SM_CYEDGE = 46
Const SM_CYFIXEDFRAME = 8
Dim tCaptionRect As RECT, tCurPos As POINTAPI, lRet As Long
Call GetWindowRect(hWnd, tCaptionRect)
With tCaptionRect
.Bottom = .Top + GetSystemMetrics(SM_CYCAPTION) + _
GetSystemMetrics(SM_CYEDGE) + _
GetSystemMetrics(SM_CYFIXEDFRAME) + _
GetSystemMetrics(SM_CYBORDER) + _
GetSystemMetrics(SM_CYDLGFRAME)
End With
Call GetCursorPos(tCurPos)
#If Win64 Then
Dim lPtr As LongLong
Call CopyMemory(lPtr, tCurPos, LenB(tCurPos))
lRet = PtInRect(tCaptionRect, lPtr)
#Else
lRet = PtInRect(tCaptionRect, tCurPos.x, tCurPos.y)
#End If
If lRet Then
IsMouseOverTitleBar = True
End If
End Function
Private Sub SaveInitPos()
#If Win64 Then
Dim hForm As LongLong
#Else
Dim hForm As Long
#End If
Dim oForm As UserForm, tFormRect As RECT
For Each oForm In VBA.UserForms
Call IUnknown_GetWindow(oForm, VarPtr(hForm))
If Not IsFormOffScreen(hForm) Then
Call GetWindowRect(hForm, tFormRect)
Call SetProp(hForm, "LEFT", tFormRect.Left)
Call SetProp(hForm, "TOP", tFormRect.Top)
End If
Next oForm
End Sub
#If Win64 Then
Private Function IsFormOffScreen(ByVal hForm As LongLong) As Boolean
#Else
Private Function IsFormOffScreen(ByVal hForm As Long) As Boolean
#End If
Const SPI_GETWORKAREA = 48
Dim tFormRect As RECT, tScreenRect As RECT, tIntersectRect As RECT
Call SystemParametersInfo(SPI_GETWORKAREA, 0&, tScreenRect, 0&)
Call GetWindowRect(hForm, tFormRect)
If IntersectRect(tIntersectRect, tFormRect, tScreenRect) = 0 Then
IsFormOffScreen = True
End If
End Function
Private Sub RestoreInitPos()
Const SWP_SHOWWINDOW = &H40
Const SWP_NOACTIVATE = &H10
Const SWP_NOSIZE = &H1
Const SWP_NOZORDER = &H4
#If Win64 Then
Dim hForm As LongLong
#Else
Dim hForm As Long
#End If
Static FormsCount As Long
Dim oForm As UserForm
If FormsCount > VBA.UserForms.Count Then
For Each oForm In VBA.UserForms
Call IUnknown_GetWindow(oForm, VarPtr(hForm))
Call SetWindowPos(hForm, 0&, CLng(GetProp(hForm, "LEFT")), CLng(GetProp(hForm, "TOP")), _
0&, 0&, SWP_NOACTIVATE + SWP_SHOWWINDOW + SWP_NOZORDER + SWP_NOSIZE)
Next oForm
End If
FormsCount = VBA.UserForms.Count
End Sub
Private Function FormsOffScreen() As Boolean
Const SM_CYCAPTION = 4
Const SM_CYBORDER = 6
Const SM_CYDLGFRAME = 8
Const SM_CYEDGE = 46
Const SM_CYFIXEDFRAME = 8
Const SPI_GETWORKAREA = 48
#If Win64 Then
Dim hForm As LongLong
#Else
Dim hForm As Long
#End If
Dim tFormRect As RECT, tScreenRect As RECT, tIntersectRect As RECT
Dim oForm As UserForm
If GetAsyncKeyState(VBA.vbKeyLButton) = 0 Then
Call SystemParametersInfo(SPI_GETWORKAREA, 0&, tScreenRect, 0&)
With tScreenRect
.Left = .Left + GetSystemMetrics(SM_CYCAPTION)
.Bottom = .Bottom - GetSystemMetrics(SM_CYCAPTION)
.Right = .Right - GetSystemMetrics(SM_CYCAPTION)
End With
For Each oForm In VBA.UserForms
Call IUnknown_GetWindow(oForm, VarPtr(hForm))
Call GetWindowRect(hForm, tFormRect)
tFormRect.Bottom = tFormRect.Top + GetSystemMetrics(SM_CYCAPTION) / 2
If IntersectRect(tIntersectRect, tFormRect, tScreenRect) = 0 Then
FormsOffScreen = True: Exit For
End If
Next oForm
End If
End Function
Private Function IsAltPlusF4() As Boolean
Dim sBuff As String * 256, lRet As Long
If GetKeyState(vbKeyMenu) And &H8000 Then
lRet = GetClassName(GetFocus, sBuff, 256)
If UCase(Left(sBuff, lRet)) = "BUTTON" Then
IsAltPlusF4 = True
End If
End If
End Function
Private Function IsCaptionClick() As Boolean
Const PM_NOREMOVE = &H0
Const WM_NCLBUTTONDOWN = &HA1
Const HTCLOSE = 20
Dim tMsg As MSG
Call WaitMessage
If PeekMessage(tMsg, 0&, 0&, 0&, PM_NOREMOVE) Then
If tMsg.message = WM_NCLBUTTONDOWN And tMsg.wParam <> HTCLOSE Then
IsCaptionClick = True
End If
End If
End Function
Private Function GetCloseButtonRect(ByVal UF As UserForm) As RECT
Const STATE_SYSTEM_INVISIBLE = &H8000&
#If Win64 Then
Dim hForm As LongLong
#Else
Dim hForm As Long
#End If
Dim oAcc As IAccessible, vAccChildren As Variant
Dim lLeft As Long, lTop As Long, lWidth As Long, lHeight As Long
Dim i As Long
Call IUnknown_GetWindow(UF, VarPtr(hForm))
Set oAcc = GetBrowserAccName(hForm)
Do
i = i + 1
Call AccessibleChildren(oAcc, 0&, 1&, vAccChildren, 0&)
If oAcc.accState(i) <> STATE_SYSTEM_INVISIBLE Then
oAcc.accLocation lLeft, lTop, lWidth, lHeight, 5&
Call SetRect(GetCloseButtonRect, lLeft, lTop, lWidth + lLeft, lHeight + lTop)
Exit Do
End If
Loop Until i > oAcc.accChildCount
End Function
#If Win64 Then
Private Function GetBrowserAccName(ByVal hWnd As LongLong) As IAccessible
#Else
Private Function GetBrowserAccName(ByVal hWnd As Long) As IAccessible
#End If
Const ID_ACCESSIBLE As String = "{618736E0-3C3D-11CF-810C-00AA00389B71}"
Const OBJID_TITLEBAR = -2
Const S_OK = &H0
Dim tGUID(0 To 3) As Long
Dim oIAc As IAccessible
If IIDFromString(StrPtr(ID_ACCESSIBLE), VarPtr(tGUID(0))) = S_OK Then
If AccessibleObjectFromWindow(hWnd, OBJID_TITLEBAR, VarPtr(tGUID(0)), oIAc) = S_OK Then
Set GetBrowserAccName = oIAc
End If
End If
End Function
Private Function IsAeroEnabled() As Boolean
Dim bEnabled As Long
Call DwmIsCompositionEnabled(bEnabled)
IsAeroEnabled = CBool(bEnabled)
End Function
Private Sub Cleanup(ByRef CurPos As POINTAPI)
Dim lTimerRet As Long, i As Long
'Debug.Print "Timer ret: ", lTimerRet
'if lTimerRet is non zero,it means the timer was safely released.
'All checkbox buttons should be automatically destroyed
'along with their respective userform owner windows.
'Nevertheless, do explicitly destroy them just in case.
lTimerRet = KillTimer(Application.hWnd, 0)
If Not oColl Is Nothing Then
For i = 1 To oColl.Count
Call DestroyWindow(oColl(i))
Next
Set oColl = Nothing
End If
CurPos.x = 0: CurPos.y = 0
'Debug.Print "checkbuttons destroyed... - done."
End Sub
Private Sub Auto_Close()
Dim oForm As UserForm
Call KillTimer(Application.hWnd, 0)
For Each oForm In VBA.UserForms
Unload oForm
Next
End Sub
2- Code Usage in the UserForm module:
VBA Code:
Option Explicit
Private Sub UserForm_Initialize()
Call AddFormToGroup(Me)
End Sub
Tested on (Win7 Pro x32bit Excel 2007) and on (Win10 x64bit Excel 2016 x64bit)
Regards.