Moving and Resizing PopUp GUIs

From AutoIt Wiki

Jump to: navigation, search

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 4 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 4 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 3 serious methods for dragging a $WS_POPUP GUI, only 2 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!

Personal tools