Moving and Resizing PopUp GUIs

From AutoIt Wiki
Jump to navigation Jump to search

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()
     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()
     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.