Moving and Resizing PopUp GUIs
Introduction
Many Autoit coders would like to use the $WS_POPUP style for a GUI but do not when they find that they can no longer move or resize the GUI because it has no title bar or borders to grab. Here are four ways to move a $WS_POPUP GUI with the mouse as well as a way to resize it.
Note: I have garnered these methods from various places on the forum and I am afraid I do not remember all the original authors of the code in this tutorial. If you see your uncredited code here, please add your own credit line.
Moving a $WS_POPUP GUI
The four methods have, as you would expect, advantages and diadvantages. Some are easier to use than others - some have side-effects on the GUI, others do not. Try them all and then decide which is best for your particular script. For all examples, the green area shows where dragging is possible and the red area (if there is one) where it is not; clicking the button will bring up a message box; and pressing "Escape" always exits the script.
Method 1: $GUI_WS_EX_PARENTDRAG
This method uses a label on the GUI to act as a drag anchor. It is easy to set up, but as the label must be active, you can run into overlap problems with other controls - just try clicking on the top half of the button or see what happens when you try to drag the GUI when the button does not fire. If you disable the label, you will see that the button works normally but you can no longer drag the GUI. So although this is the simplest method, it is the least satisfactory - although it can be useful in certain cases.
#include <GuiconstantsEx.au3>
#include <WindowsConstants.au3>
HotKeySet("{ESC}", "On_Exit")
$hGUI = GUICreate("X", 100, 100, -1, -1, $WS_POPUP)
GUISetBkColor(0xFF0000, $hGUI)
$hLabel = GUICtrlCreateLabel("", 0, 0, 100, 50, -1, $GUI_WS_EX_PARENTDRAG)
GUICtrlSetBkColor(-1, 0x00FF00)
;GUICtrlSetState(-1, $GUI_DISABLE)
$hButton = GUICtrlCreateButton("Test", 10, 35, 80, 30)
GUISetState()
While 1
Switch GUIGetMsg()
Case $hButton
On_Button()
EndSwitch
WEnd
Func On_Button()
MsgBox(0, "Hi", "Button Pressed")
EndFunc ;==>On_Button
Func On_Exit()
Exit
EndFunc ;==>On_Exit
Method 2: Sending the $SC_DRAGMOVE message
Here we send a message when the primary mouse button is pressed to tell Windows to drag the GUI with the mouse. Note that the button works without problem.
; Original code - martin
#include <GuiconstantsEx.au3>
#include <WindowsConstants.au3>
#include <SendMessage.au3>
Global Const $SC_DRAGMOVE = 0xF012
HotKeySet("{ESC}", "On_Exit")
$hGUI = GUICreate("X", 100, 100, -1, -1, $WS_POPUP)
GUISetBkColor(0x00FF00, $hGUI)
$hButton = GUICtrlCreateButton("Test", 10, 35, 80, 30)
GUISetState()
While 1
Switch GUIGetMsg()
Case $GUI_EVENT_PRIMARYDOWN
_SendMessage($hGUI, $WM_SYSCOMMAND, $SC_DRAGMOVE, 0)
Case $hButton
On_Button()
EndSwitch
WEnd
Func On_Button()
MsgBox(0, "Hi", "Button Pressed")
EndFunc ;==>On_Button
Func On_Exit()
Exit
EndFunc ;==>On_Exit
Method 3: Sending the $WM_NCLBUTTONDOWN message
In this example, we also detect when the primary mouse button is down. We then fool Windows into believing that the cursor is not in the client area of the GUI but is actually in the title bar - Windows reacts as we hoped by dragging the GUI. Again the button works without problem as Windows realises it is a control.
#include <GuiconstantsEx.au3>
#include <WindowsConstants.au3>
#include <SendMessage.au3>
HotKeySet("{ESC}", "On_Exit")
$hGUI = GUICreate("X", 100, 100, -1, -1, $WS_POPUP)
GUISetBkColor(0x00FF00, $hGUI)
$hButton = GUICtrlCreateButton("Test", 10, 35, 80, 30)
GUISetState()
While 1
Switch GUIGetMsg()
Case $GUI_EVENT_PRIMARYDOWN
On_Drag()
Case $hButton
On_Button()
EndSwitch
WEnd
Func On_Drag()
Local $aCurInfo = GUIGetCursorInfo($hGUI)
If $aCurInfo[4] = 0 Then ; Mouse not over a control
DllCall("user32.dll", "int", "ReleaseCapture")
_SendMessage($hGUI, $WM_NCLBUTTONDOWN, $HTCAPTION, 0)
EndIf
EndFunc ;==>On_Drag
Func On_Exit()
Exit
EndFunc ;==>On_Exit
Func On_Button()
MsgBox(0, "Hi", "Button Pressed")
EndFunc ;==>On_Button
Note: The remainder of this tutorial assumes knowledge of the GUIRegisterMsg command. If you are not clear on the use of this command, please read the GUIRegisterMsg tutorial before continuing.
Method 4: Handling the $WM_NCHITTEST message
This final method intercepts the $WM_NCHITTEST message, which basically tells Windows what part of the GUI is under the mouse when a button is pressed. If the mouse is over the title bar, then Windows will drag the GUI. In this script we tell Windows that the top half of the GUI is a title bar and so would Windows please drag as normal. The coloured label is just there to differentiate between the 2 areas and has been disabled so as not to interfere with the button - which works normally as Windows realises it is a control and not part of the GUI.
#include <GuiconstantsEx.au3>
#include <WindowsConstants.au3>
#include <SendMessage.au3>
HotKeySet("{ESC}", "On_Exit")
$hGUI = GUICreate("X", 100, 100, -1, -1, $WS_POPUP)
GUISetBkColor(0x00FF00, $hGUI)
GUICtrlCreateLabel("", 0, 50, 100, 50)
GUICtrlSetBkColor(-1, 0xFF0000)
GUICtrlSetState(-1, $GUI_DISABLE)
$hButton = GUICtrlCreateButton("Test", 10, 35, 80, 30)
GUISetState()
GUIRegisterMsg($WM_NCHITTEST, "_MY_NCHITTEST")
While 1
Switch GUIGetMsg()
Case $hButton
On_Button()
EndSwitch
WEnd
; Original code - Prog@ndy
Func _MY_NCHITTEST($hWnd, $uMsg, $wParam, $lParam)
Switch $hWnd
Case $hGUI
Local $aPos = WinGetPos($hWnd) ; Check if mouse is over top 50 pixels
If Abs(BitAND(BitShift($lParam, 16), 0xFFFF) - $aPos[1]) < 50 Then Return $HTCAPTION
EndSwitch
Return $GUI_RUNDEFMSG
EndFunc ;==>_MY_NCHITTEST
Func On_Button()
MsgBox(0, "Hi", "Button Pressed")
EndFunc ;==>On_Button
Func On_Exit()
Exit
EndFunc ;==>On_Exit
Resizing a $WS_POPUP GUI
To resize a GUI without borders, we have to fool Windows into thinking that the borders actually exist. We do this by telling Windows to change the cursor to the "resize" type if the mouse is placed over the margin of the GUI. Then when primary mouse button is pressed, we check if the cursor has been changed and, if it has, tell Windows to resize the GUI.
The various message handlers interact as follows:
When we receive a $WM_MOUSEMOVE message, we check if the mouse is over a border by calling the _Check_Border function, which returns a code to indicate which cursor type is required, and then using that return to set the correct cursor type within the _SetCursor function.
Then if a $WM_LBUTTONDOWN message is received we again check the _Check_Border function and, if we are over a border, tell Windows to resize the GUI as the mouse is dragged.
; Original code - martin
#include <GuiConstantsEx.au3>
#include <Windowsconstants.au3>
#include <SendMessage.au3>
HotKeySet("{ESC}", "On_Exit")
; Set distance from edge of window where resizing is possible
Global Const $iMargin = 4
; Create GUI
$hGUI = GUICreate("Y", 100, 100, -1, -1, $WS_POPUP)
GUISetBkColor(0xFF0000)
GUISetState()
; Register message handlers
GUIRegisterMsg($WM_LBUTTONDOWN, "_WM_LBUTTONDOWN") ; For resize
GUIRegisterMsg($WM_MOUSEMOVE, "_SetCursor") ; For cursor type change
While 1
Sleep(10)
WEnd
; Check cursor type and resize/drag window as required
Func _WM_LBUTTONDOWN($hWnd, $iMsg, $wParam, $lParam)
Local $iCursorType = _GetBorder()
If $iCursorType > 0 Then ; Cursor is set to resizing style
$iResizeType = 0xF000 + $iCursorType
_SendMessage($hGUI, $WM_SYSCOMMAND, $iResizeType, 0)
EndIf
EndFunc ;==>WM_LBUTTONDOWN
; Set cursor to correct resizing form if mouse is over a border
Func _SetCursor()
Local $iCursorID
Switch _GetBorder()
Case 0
$iCursorID = 2
Case 1, 2
$iCursorID = 13
Case 3, 6
$iCursorID = 11
Case 5, 7
$iCursorID = 10
Case 4, 8
$iCursorID = 12
EndSwitch
GUISetCursor($iCursorID, 1)
EndFunc ;==>SetCursor
; Determines if mouse cursor over a border
Func _GetBorder()
Local $aCurInfo = GUIGetCursorInfo()
Local $aWinPos = WinGetPos($hGUI)
Local $iSide = 0
Local $iTopBot = 0
If $aCurInfo[0] < $iMargin Then $iSide = 1
If $aCurInfo[0] > $aWinPos[2] - $iMargin Then $iSide = 2
If $aCurInfo[1] < $iMargin Then $iTopBot = 3
If $aCurInfo[1] > $aWinPos[3] - $iMargin Then $iTopBot = 6
Return $iSide + $iTopBot
EndFunc ;==>_GetBorder
Func On_Exit()
Exit
EndFunc
Moving and resizing a $WS_POPUP GUI
Of the three serious methods for dragging a $WS_POPUP GUI, only two can be combined with the resize code. The $WM_NCHITTEST message version is unsuitable as it prevents the detection of the simulated borders. The other 2 methods both use the $GUI_EVENT_PRIMARYDOWN message within the GUIGetMsg loop to detect the primary mouse button being pressed, but we could also use the $WM_LBUTTONDOWN message we use when resizing the GUI to do the same thing. All we need to do is to check the cursor position and so the required cursor type: if it is greater than 0 we have a resize cursor - if it is 0 then we have a normal cursor and should drag the whole GUI.
As an added bonus because you have been kind enough to read until there, the following examples also show how to set the maximum and minimum size of a resizable GUI via the $WM_GETMINMAXINFO message. We simply make sure we set our own limits every time Windows checks to see when it should stop resizing.
So here are the 2 scripts - first using the $SC_DRAGMOVE message
#include <GuiConstantsEx.au3>
#include <Windowsconstants.au3>
#include <SendMessage.au3>
Global Const $SC_DRAGMOVE = 0xF012
HotKeySet("{ESC}", "On_Exit")
; Set distance from edge of window where resizing is possible
Global Const $iMargin = 4
; Set max and min GUI sizes
Global Const $iGUIMinX = 50, $iGUIMinY = 50, $iGUIMaxX = 300, $iGUIMaxY = 300
; Create GUI
$hGUI = GUICreate("Y", 100, 100, -1, -1, $WS_POPUP)
GUISetBkColor(0x00FF00)
GUISetState()
; Register message handlers
GUIRegisterMsg($WM_MOUSEMOVE, "_SetCursor") ; For cursor type change
GUIRegisterMsg($WM_LBUTTONDOWN, "_WM_LBUTTONDOWN") ; For resize/drag
GUIRegisterMsg($WM_GETMINMAXINFO, "_WM_GETMINMAXINFO") ; For GUI size limits
While 1
Sleep(10)
WEnd
; Set cursor to correct resizing form if mouse is over a border
Func _SetCursor()
Local $iCursorID
Switch _Check_Border()
Case 0
$iCursorID = 2
Case 1, 2
$iCursorID = 13
Case 3, 6
$iCursorID = 11
Case 5, 7
$iCursorID = 10
Case 4, 8
$iCursorID = 12
EndSwitch
GUISetCursor($iCursorID, 1)
EndFunc ;==>SetCursor
; Check cursor type and resize/drag window as required
Func _WM_LBUTTONDOWN($hWnd, $iMsg, $wParam, $lParam)
Local $iCursorType = _Check_Border()
If $iCursorType > 0 Then ; Cursor is set to resizing style so send appropriate resize message
$iResizeType = 0xF000 + $iCursorType
_SendMessage($hGUI, $WM_SYSCOMMAND, $iResizeType, 0)
Else
Local $aCurInfo = GUIGetCursorInfo($hGUI)
If $aCurInfo[4] = 0 Then ; Mouse not over a control
_SendMessage($hGUI, $WM_SYSCOMMAND, $SC_DRAGMOVE, 0)
EndIf
EndIf
EndFunc ;==>WM_LBUTTONDOWN
; Determines if mouse cursor over a border
Func _Check_Border()
Local $aCurInfo = GUIGetCursorInfo()
If @error Then Return -1
Local $aWinPos = WinGetPos($hGUI)
Local $iSide = 0
Local $iTopBot = 0
If $aCurInfo[0] < $iMargin Then $iSide = 1
If $aCurInfo[0] > $aWinPos[2] - $iMargin Then $iSide = 2
If $aCurInfo[1] < $iMargin Then $iTopBot = 3
If $aCurInfo[1] > $aWinPos[3] - $iMargin Then $iTopBot = 6
Return $iSide + $iTopBot
EndFunc ;==>_Check_Border
; Set min and max GUI sizes
Func _WM_GETMINMAXINFO($hWnd, $iMsg, $wParam, $lParam)
$tMinMaxInfo = DllStructCreate("int;int;int;int;int;int;int;int;int;int", $lParam)
DllStructSetData($tMinMaxInfo, 7, $iGUIMinX)
DllStructSetData($tMinMaxInfo, 8, $iGUIMinY)
DllStructSetData($tMinMaxInfo, 9, $iGUIMaxX)
DllStructSetData($tMinMaxInfo, 10, $iGUIMaxY)
Return 0
EndFunc ;==>_WM_GETMINMAXINFO
Func On_Exit()
Exit
EndFunc
And now using the $WM_NCLBUTTONDOWN message
#include <GuiConstantsEx.au3>
#include <Windowsconstants.au3>
#include <SendMessage.au3>
HotKeySet("{ESC}", "On_Exit")
; Set distance from edge of window where resizing is possible
Global Const $iMargin = 4
; Set max and min GUI sizes
Global Const $iGUIMinX = 50, $iGUIMinY = 50, $iGUIMaxX = 300, $iGUIMaxY = 300
; Create GUI
$hGUI = GUICreate("Y", 100, 100, -1, -1, $WS_POPUP)
GUISetBkColor(0x00FF00)
GUISetState()
; Register message handlers
GUIRegisterMsg($WM_LBUTTONDOWN, "_WM_LBUTTONDOWN") ; For resize/drag
GUIRegisterMsg($WM_MOUSEMOVE, "_SetCursor") ; For cursor type change
GUIRegisterMsg($WM_GETMINMAXINFO, "_WM_GETMINMAXINFO") ; For GUI size limits
While 1
Sleep(10)
WEnd
; Check cursor type and resize/drag window as required
Func _WM_LBUTTONDOWN($hWnd, $iMsg, $wParam, $lParam)
Local $iCursorType = _GetBorder()
If $iCursorType > 0 Then ; Cursor is set to resizing style
$iResizeType = 0xF000 + $iCursorType
_SendMessage($hGUI, $WM_SYSCOMMAND, $iResizeType, 0)
Else
Local $aCurInfo = GUIGetCursorInfo($hGUI)
If $aCurInfo[4] = 0 Then ; Mouse not over a control
DllCall("user32.dll", "int", "ReleaseCapture")
_SendMessage($hGUI, $WM_NCLBUTTONDOWN, $HTCAPTION, 0)
EndIf
EndIf
EndFunc ;==>WM_LBUTTONDOWN
; Set cursor to correct resizing form if mouse is over a border
Func _SetCursor()
Local $iCursorID
Switch _GetBorder()
Case 0
$iCursorID = 2
Case 1, 2
$iCursorID = 13
Case 3, 6
$iCursorID = 11
Case 5, 7
$iCursorID = 10
Case 4, 8
$iCursorID = 12
EndSwitch
GUISetCursor($iCursorID, 1)
EndFunc ;==>SetCursor
; Determines if mouse cursor over a border
Func _GetBorder()
Local $aCurInfo = GUIGetCursorInfo()
If @error Then Return -1
Local $aWinPos = WinGetPos($hGUI)
Local $iSide = 0
Local $iTopBot = 0
If $aCurInfo[0] < $iMargin Then $iSide = 1
If $aCurInfo[0] > $aWinPos[2] - $iMargin Then $iSide = 2
If $aCurInfo[1] < $iMargin Then $iTopBot = 3
If $aCurInfo[1] > $aWinPos[3] - $iMargin Then $iTopBot = 6
Return $iSide + $iTopBot
EndFunc ;==>_GetBorder
; Set min and max GUI sizes
Func _WM_GETMINMAXINFO($hWnd, $iMsg, $wParam, $lParam)
$tMinMaxInfo = DllStructCreate("int;int;int;int;int;int;int;int;int;int", $lParam)
DllStructSetData($tMinMaxInfo, 7, $iGUIMinX)
DllStructSetData($tMinMaxInfo, 8, $iGUIMinY)
DllStructSetData($tMinMaxInfo, 9, $iGUIMaxX)
DllStructSetData($tMinMaxInfo, 10, $iGUIMaxY)
Return 0
EndFunc ;==>_WM_GETMINMAXINFO
Func On_Exit()
Exit
EndFunc
Summary
Although it seems impossible to move and resize $WS_POPUP style GUIs because they lack the usual title bar and borders, you can se that it is not that difficult to do it. All we do is mimic what Windows does to a normal GUI.