Page 1 of 1

Disable Moving a MsgBox

Posted: 23 Mar 2017, 09:27
by jNizM
Hi all,

how can I disable moving on a MsgBox.

With the MessageBox function (msdn) I can bind a MsgBox to a GUI (owner -> child) and stop other activities with the gui.
With the GuiDisableMove function I can disable moving my GUI but how can I stop moving a MsgBox (without create a MsgBox like GUI)?

Code: Select all

; GLOBAL SETTINGS ===============================================================================================================

#NoEnv
#SingleInstance Force
SetBatchLines -1

; GUI ===========================================================================================================================

Gui, +hWndhMyGUI
Gui, Margin, 10, 10
Gui, Add, Button, xm-1 ym w402 h200 gBUTTON_MSGBOX, % "Click me!"
Gui, Add, Edit, xm y+9 w400 0x801 vMyEdit
Gui, Show, AutoSize
GuiDisableMove(hMyGUI)
GuiDisableCloseButton(hMyGUI)
return

; SCRIPT ========================================================================================================================

BUTTON_MSGBOX:
    ret := MessageBox(hMyGUI, "Random Title", "YES or NO?", 0x24)

    GuiControl,, MyEdit, % (msg = 7) ? "NO!" : "YES!"
return

; FUNCTIONS =====================================================================================================================

MessageBox(handle, title, text, options)
{
    return DllCall("user32\MessageBox", "ptr", handle, "str", text, "str", title, "uint", options)
}

GuiDisableMove(handle)
{
    hMenu := DllCall("user32\GetSystemMenu", "ptr", handle, "int", false, "ptr")
    DllCall("user32\RemoveMenu", "ptr", hMenu, "uint", 0xf010, "uint", 0)
    return DllCall("user32\DrawMenuBar", "ptr", handle)
}

GuiDisableCloseButton(handle)
{
    hMenu := DllCall("user32\GetSystemMenu", "ptr", handle, "int", false, "ptr")
    DllCall("user32\EnableMenuItem", "ptr", hMenu, "uint", 0xf060, "uint", 0x3)
    return DllCall("user32\DrawMenuBar", "ptr", handle)
}

; EXIT ==========================================================================================================================

GuiEscape:
GuiClose:
ExitApp

; ===============================================================================================================================

Re: Disable Moving a MsgBox

Posted: 23 Mar 2017, 10:47
by 4GForce
jNizM wrote:how can I disable moving on a MsgBox.
My solution is kinda ugly, but it works ...

Code: Select all

BUTTON_MSGBOX:
	SetTimer DoSomething, -25
    ret := MessageBox(hMyGUI, "Random Title", "YES or NO?", 0x24)
    GuiControl,, MyEdit, % (ret = 7) ? "NO!" : "YES!"
return

DoSomething:
	; WinWait % "Random Title"
	WinSet Style, -0x00800000, % "Random Title"
Return
Somehow I was more successfull with a fixed timer delay than with -1 and WinWait but I guess the timing may vary depending on the system it runs on.
I would guess that other styles might work as well, as long as it removes the borders.

Re: Disable Moving a MsgBox

Posted: 23 Mar 2017, 12:39
by Helgef
A MsgBox has the ahk_class #32770, for those, we can do something like this,

Code: Select all

MsgBox, move me!
*~LButton::mbstop()
*~LButton up::BlockInput, MouseMoveOff
mbstop(){
	static mbClass:="#32770" ; I do not know how reliable this is.
	MouseGetPos,,,wum
	WinGetClass, WinClassUM, % "ahk_id " wum
	if (WinClassUM == mbClass)
		BlockInput, MouseMove
	return
}
Cheers.

Re: Disable Moving a MsgBox

Posted: 23 Mar 2017, 14:50
by MilesAhead
static mbClass:="#32770" ; I do not know how reliable this is.
Nearly every dialog created by Windows uses the class #32770. So you may want to use multiple criteria to narrow down the target. As an example, I wrote a dialog based utility in C++ that calculates MD5sums for 1 or more files, or a folder tree. I has the class #32770. :)

Re: Disable Moving a MsgBox

Posted: 23 Mar 2017, 14:52
by 4GForce
Helgef wrote:A MsgBox has the ahk_class #32770, for those, we can do something like this,
Vnice blocking the mousemove on left click ... but sadly ... it can be move by right-clicking, select Move ... then press spacebar and move with the arrow keys ... :shock:

Code: Select all

MsgBox % "Move me !"
#IfWinActive ahk_class #32770
*~LButton::BlockInput MouseMove
*~LButton up::BlockInput MouseMoveOff
#IfWinActive

Re: Disable Moving a MsgBox

Posted: 23 Mar 2017, 14:54
by Helgef
Good to know MilesAhead, thanks. :thumbup:

Re: Disable Moving a MsgBox

Posted: 23 Mar 2017, 14:58
by Helgef
4GForce wrote:it can be move by right-clicking, select Move ... then press spacebar and move with the arrow keys ... :shock:

Code: Select all

MsgBox % "Move me !"
#IfWinActive ahk_class #32770
*~LButton::BlockInput MouseMove
*~LButton up::BlockInput MouseMoveOff'
*RButton::return
*Space::return
#IfWinActive
:D

Re: Disable Moving a MsgBox

Posted: 23 Mar 2017, 15:12
by 4GForce
Helgef wrote: :D
Well now its my fault ... but #IfWinActive can somehow be bypassed ( hover icon in taskbar, then hover the preview, wait a sec then hold left click ... move to the window and release left-click and click hold again to move it :wtf: )
So MouseGetPos might be necessary after all.

Edit: Win7 ... and actually if any other window is active when the left click is done ... it wont proc the binding

Re: Disable Moving a MsgBox

Posted: 23 Mar 2017, 15:43
by MilesAhead
Helgef wrote:Good to know MilesAhead, thanks. :thumbup:
You are most welcome.

Image

Re: Disable Moving a MsgBox

Posted: 23 Mar 2017, 16:58
by qwerty12
Yet another one. I won't vouch for the reliability of this code...

Code: Select all

; GLOBAL SETTINGS ===============================================================================================================

#NoEnv
#SingleInstance Force
SetBatchLines -1

; Hook init =====================================================================================================================

{ ; WTF
	OnExit("AtExit") 
}
/*
useSetWinEventHook := False
if (useSetWinEventHook)
{
	hWinEventHook := DllCall("SetWinEventHook", "UInt", (EVENT_SYSTEM_DIALOGSTART := 0x0010), "UInt", EVENT_SYSTEM_DIALOGSTART, "Ptr", 0, "Ptr", (lpCallback := RegisterCallback("OnDialogStart", "")), "UInt", DllCall("GetCurrentProcessId", "UInt"), "UInt", DllCall("GetCurrentThreadId", "UInt"), "UInt", WINEVENT_OUTOFCONTEXT := 0x0000)
} 
else
*/
{
	wndhook := DllCall("SetWindowsHookEx", "Int", (WH_CALLWNDPROCRET := 12), "Ptr", (lpCallback := RegisterCallback("CallWndProc", "")), "Ptr", 0, "UInt", DllCall("GetCurrentThreadId", "UInt"), "Ptr")
}

; GUI ===========================================================================================================================

Gui, +hWndhMyGUI
Gui, Margin, 10, 10
Gui, Add, Button, xm-1 ym w402 h200 gBUTTON_MSGBOX, % "Click me!"
Gui, Add, Edit, xm y+9 w400 0x801 vMyEdit
Gui, Show, AutoSize
GuiDisableMove(hMyGUI)
GuiDisableCloseButton(hMyGUI)
return

; SCRIPT ========================================================================================================================

BUTTON_MSGBOX:
    ret := MessageBox(hMyGUI, "Random Title", "YES or NO?", 0x24)
;	FileSelectFolder C

    GuiControl,, MyEdit, % (ret = 7) ? "NO!" : "YES!"
return

; FUNCTIONS =====================================================================================================================

MessageBox(handle, title, text, options)
{
    return DllCall("user32\MessageBox", "ptr", handle, "str", text, "str", title, "uint", options)
}

GuiDisableMove(handle)
{
    hMenu := DllCall("user32\GetSystemMenu", "ptr", handle, "int", false, "ptr")
    DllCall("user32\RemoveMenu", "ptr", hMenu, "uint", 0xf010, "uint", 0)
    return DllCall("user32\DrawMenuBar", "ptr", handle)
}

GuiDisableCloseButton(handle)
{
    hMenu := DllCall("user32\GetSystemMenu", "ptr", handle, "int", false, "ptr")
    DllCall("user32\EnableMenuItem", "ptr", hMenu, "uint", 0xf060, "uint", 0x3)
    return DllCall("user32\DrawMenuBar", "ptr", handle)
}

; EXIT ==========================================================================================================================

GuiEscape:
GuiClose:
ExitApp

; Callbacks =====================================================================================================================

AtExit()
{
	global hWinEventHook, lpfnWinEventProc, wndhook
	if (hWinEventHook)
		DllCall("UnhookWinEvent", "Ptr", hWinEventHook), hWinEventHook := 0
	else if (wndhook) 
		DllCall("UnhookWindowsHookEx", "Ptr", wndhook), wndhook := 0
	if (lpCallback)
		DllCall("GlobalFree", "Ptr", lpCallback, "Ptr"), lpCallback := 0	
	return 0
}

/*
OnDialogStart(hWinEventHook, event, hWnd, idObject, idChild, dwEventThread, dwmsEventTime) {
	Critical 1000
	GuiDisableMove(hWnd)
	Critical Off
}
*/

CallWndProc(nCode, wParam, lParam)
{
	Critical 1000
	if (nCode >= 0) { ; HC_ACTION
		cwpmessage := NumGet(lParam+0, A_PtrSize * 3, "UInt")

		if (cwpmessage == 0x0110) { ; WM_INITDIALOG
			cwplparam := NumGet(lParam+0, A_PtrSize, "Ptr")
			hWnd := NumGet(lParam+0, A_PtrSize * 4, "Ptr")
			if (cwplparam && DllCall(A_PtrSize == 8 ? "GetWindowLongPtr" : "GetWindowLong", "Ptr", hWnd, "Int", -21, "Ptr") == cwplparam) { ; assumptions abound from this point
				static cbMSGBOXPARAMS := A_PtrSize * 10
				if (NumGet(cwplparam+0,, "UInt") == cbMSGBOXPARAMS)
					GuiDisableMove(hWnd)
			}
		}
	}
	return DllCall("CallNextHookEx", "Ptr", 0, "Int", nCode, "Ptr", wParam, "Ptr", lParam, "Ptr")
}

; ===============================================================================================================================

Re: Disable Moving a MsgBox

Posted: 23 Mar 2017, 18:26
by 4GForce
qwerty12 wrote:Yet another one. I won't vouch for the reliability of this code...
Well even tho it seems a bit hardcore for such a small issue ... it works fine and having a ; WTF in the 1st 10 lines makes it even better :HeHe:

Re: Disable Moving a MsgBox

Posted: 23 Mar 2017, 19:12
by qwerty12
4GForce wrote:Well even tho it seems a bit hardcore for such a small issue
I like having some degree of certainty in knowing I'm getting what I'm asking for. If WM_INITDIALOG is caught, it's a good indicator that a dialog of some sort is about to be shown - and I get to have the MessageBox's window handle. My way of making sure it only (mostly) affects MessageBoxes only is pretty bad, though :oops:
having a ; WTF in the 1st 10 lines makes it even better :HeHe:
Place the OnExit line out of its own scope and see what the error message is. I guess I've messed something up with the commenting, but I don't know enough/can't be bothered to find out what it is...

Re: Disable Moving a MsgBox

Posted: 24 Mar 2017, 01:53
by jNizM
Your WinAPI knowledge is impressive qwerty12 ;)

I found 2 more "magic numbers" for NumGet(cwplparam+0, "uint")

Code: Select all

                     |     x64     |     x86     |
--------------------------------------------------
MsgBox / MessageBox  |         80  |         40  |
FileSelectFolder     | 2922933792  | 1960033372  |
FileSelectFile       | 2939608016  | 1986270796  |

Re: Disable Moving a MsgBox

Posted: 24 Mar 2017, 07:48
by qwerty12
Hi jNizM,

The MessageBox-only detection works by chance and makes assumptions that could break in future versions of Windows - a MessageBox's WM_INITDIALOG handler sets the GWL_USERDATA to what it gets in the lParam, which I take advantage of checking for. But so does whatever control is used by FileSelectFolder...
It does turn out, however, that the structure pointed to by MessageBox's WM_INITDIALOG lParam has contains the MSGBOXPARAMS used to construct the MessageBox right at the beginning (and you can specify your own) and the first member of that is the MSGBOXPARAMS's size. It's not a great way of checking: what if I hit 40/80 by chance (maybe a different dialog box happens to use a structure of the same size)...

But my WinAPI knowledge isn't that that great and I don't know of any better ways to determine with certainty that we are dealing with a MessageBox. Comparing by dialog proc address would've been cool but you need debug symbols for that...

Re: Disable Moving a MsgBox

Posted: 24 Mar 2017, 09:47
by jNizM
I tried Subclassing but the message WM_INITDIALOG = 0x110 does not show up

Re: Disable Moving a MsgBox

Posted: 24 Mar 2017, 12:17
by qwerty12
(That's a really nice tutorial; I've never subclassed anything before, but the code and tutorial makes doing so easy. Thank you, just me. Google Translate does a good job on the page :-))

Hmm, hopefully I'm not misunderstanding something, but why not use the hook to subclass the MessageBox after WM_INITDIALOG is called? I think only a specific window can be subclassed, as opposed to an entire class, so you need some way to get that specific MessageBox window handle in the first place, and I can't think of any benefits that modifying WM_INITDIALOG brings. I tried my hand at subclassing a MessageBox window and stopping the window from being moved by subclassing and targeting WM_WINDOWPOSCHANGING (code also by just me - the nice thing about this approach is that it stops AltDrag from working, unlike GuiDisableMove):

Code: Select all

; GLOBAL SETTINGS ===============================================================================================================

#NoEnv
#SingleInstance Force
SetBatchLines -1

; GUI ===========================================================================================================================

Gui, +hWndhMyGUI
Gui, Margin, 10, 10
Gui, Add, Button, xm-1 ym w402 h200 gBUTTON_MSGBOX, % "Click me!"
Gui, Add, Edit, xm y+9 w400 0x801 vMyEdit
Gui, Show, AutoSize
GuiDisableMove(hMyGUI)
GuiDisableCloseButton(hMyGUI)
return

; SCRIPT ========================================================================================================================

BUTTON_MSGBOX:
    ret := MessageBox(hMyGUI, "Random Title", "YES or NO?", 0x24)
;	FileSelectFolder C

    GuiControl,, MyEdit, % (ret = 7) ? "NO!" : "YES!"
return

; FUNCTIONS =====================================================================================================================

MessageBox(handle, title, text, options)
{
	static WH_CALLWNDPROCRET := 12, lpCallWndProc := RegisterCallback("CallWndProc", "")
	wndhook := DllCall("SetWindowsHookEx", "Int", WH_CALLWNDPROCRET, "Ptr", lpCallWndProc, "Ptr", 0, "UInt", DllCall("GetCurrentThreadId", "UInt"), "Ptr")
    ret := DllCall("user32\MessageBox", "ptr", handle, "str", text, "str", title, "uint", options)
    if (wndhook)
		DllCall("UnhookWindowsHookEx", "Ptr", wndhook)
    return ret
}

GuiDisableMove(handle)
{
    hMenu := DllCall("user32\GetSystemMenu", "ptr", handle, "int", false, "ptr")
    DllCall("user32\RemoveMenu", "ptr", hMenu, "uint", 0xf010, "uint", 0)
    return DllCall("user32\DrawMenuBar", "ptr", handle)
}

GuiDisableCloseButton(handle)
{
    hMenu := DllCall("user32\GetSystemMenu", "ptr", handle, "int", false, "ptr")
    DllCall("user32\EnableMenuItem", "ptr", hMenu, "uint", 0xf060, "uint", 0x3)
    return DllCall("user32\DrawMenuBar", "ptr", handle)
}

; ======================================================================================================================
; SubclassControl    Installs, updates, or removes the subclass callback for the specified control.
;                    Installiert, ändert oder entfernt den Subclass-Funktionsaufruf für das angegebene Control.
; Author:            just me (www.ahkscript.org)
; ======================================================================================================================
SubclassControl(HCTRL, FuncName, RefData := 0) {
   Static ControlCB := []
   If ControlCB.HasKey(HCTRL) {
      DllCall("Comctl32.dll\RemoveWindowSubclass", "Ptr", HCTRL, "Ptr", ControlCB[HCTRL], "Ptr", HCTRL)
      DllCall("Kernel32.dll\GlobalFree", "Ptr", ControlCB[HCTRL], "Ptr")
      ControlCB.Remove(HCTRL, "")
      If (FuncName = "")
         Return True
   }
   If !DllCall("User32.dll\IsWindow", "Ptr", HCTRL, "UInt")
   Or !IsFunc(FuncName) || (Func(FuncName).MaxParams <> 6)
   Or !(CB := RegisterCallback(FuncName, , 6))
      Return False
   If !DllCall("Comctl32.dll\SetWindowSubclass", "Ptr", HCTRL, "Ptr", CB, "Ptr", HCTRL, "Ptr", RefData)
      Return (DllCall("Kernel32.dll\GlobalFree", "Ptr", CB, "Ptr") & 0)
   Return (ControlCB[HCTRL] := CB)
}

; EXIT ==========================================================================================================================

GuiEscape:
GuiClose:
ExitApp

; Callbacks =====================================================================================================================

NotAllowed(HWND, Msg, wParam, lParam, SubclassID, RefData) {
   ; WM_WINDOWPOSCHANGING = 0x0046, WM_DESTROY := 0x0002
   If (Msg == 0x0046) {
		; just me: https://autohotkey.com/board/topic/96671-prevent-window-from-moving/
		Static SWP_NOMOVE := 2
		Static SWP_NOSIZE := 1
		Static OffFlags := A_PtrSize + A_PtrSize + 16 ; 24 (AHK x86) | 32 (AHK x64)
		NumPut(NumGet(lParam + OffFlags, "UInt") | SWP_NOMOVE, lParam + OffFlags, "UInt")
		Return 0
   } else if (Msg == 0x0002) {
		DllCall("Comctl32.dll\RemoveWindowSubclass", "Ptr", HWND, "Ptr", A_EventInfo, "Ptr", SubclassID)
		funcobj := Func("SubclassControl").Bind(HWND, "")
		SetTimer % funcobj, -100 ; eurgh - better off doing this in the hook? IIRC, WM_NCDESTROY follows, so it doesn't matter that it's a WH_CALLWNDPROCRET hook
   }
   Return DllCall("Comctl32.dll\DefSubclassProc", "Ptr", HWND, "UInt", Msg, "Ptr", wParam, "Ptr", lParam)
}

CallWndProc(nCode, wParam, lParam)
{
	Critical 1000
	if (nCode >= 0) { ; HC_ACTION
		cwpmessage := NumGet(lParam+0, A_PtrSize * 3, "UInt")

		if (cwpmessage == 0x0110) { ; WM_INITDIALOG
			cwplparam := NumGet(lParam+0, A_PtrSize, "Ptr")
			hWnd := NumGet(lParam+0, A_PtrSize * 4, "Ptr")

			prevDetectHiddenWindows := A_DetectHiddenWindows
			DetectHiddenWindows On ; grr
			WinGetClass strClass, ahk_id %hWnd%
			DetectHiddenWindows %prevDetectHiddenWindows%
			
			if (cwplparam && strClass == "#32770" && DllCall(A_PtrSize == 8 ? "GetWindowLongPtr" : "GetWindowLong", "Ptr", hWnd, "Int", -21, "Ptr") == cwplparam) { ; assumptions abound from this point
				static cbMSGBOXPARAMS := A_PtrSize * 10
				if (NumGet(cwplparam+0,, "UInt") == cbMSGBOXPARAMS)
					SubclassControl(hWnd, "NotAllowed")
			}
		}
	}
	return DllCall("CallNextHookEx", "Ptr", 0, "Int", nCode, "Ptr", wParam, "Ptr", lParam, "Ptr")
}

; ===============================================================================================================================

Re: Disable Moving a MsgBox

Posted: 24 Mar 2017, 12:58
by Helgef
@qwert12, :thumbup: Interesting stuff.
@Topic, I just remembered, I did a MsgBoxPos function in connection to making this, they use similar methods. Anyways, it was easy to add the movement disabling.

Code: Select all

; Test code, should demonstrate basic usage.
MsgBox, % "Return: " MsgBoxPos(4,,"...and text", 3,{fixed:true})
MsgBoxPos(1, "Title...","...and text", 3,{x:50,w:550,fixed:true})
MsgBoxPos(2, "Title...","...and text", 3,{y:250,x:50,fixed:true})


MsgBoxPos(Options:="", Title:="", Text:="", Timeout:="",Pos:="") {
	; Pass the regular msgbox parameters: Msgbox [, Options, Title, Text, Timeout]
	; and Pos:={[x:x-pos,y:y-pos,w:widht,h:height,fixed:bool]} to set position and height of the msgbox.
	; fixed:true disables movement of the msgbox. 
	; Author: Helgef
	; Disclaimer: Not finished or tested.
	static r := DllCall("RegisterShellHookWindow", "Uptr", A_ScriptHwnd)
	static id:= DllCall("RegisterWindowMessage", "Str", "SHELLHOOK")
	static HSHELL_WINDOWCREATED:=1
	static p
	wParam:=Options, lParam:=Title, msg:=Text, hwnd:=Timeout ; For clarity
	if (hwnd==A_ScriptHwnd) {
		Critical,On
		if (wParam!=HSHELL_WINDOWCREATED || msg!=id || !lParam)
			return
		WinGetTitle, mbt, % "ahk_id " lParam
		WinGetClass, class, % "ahk_id " lParam
		WinGet,exe,ProcessName, % "ahk_id " lParam
		if (mbt . class . exe != p.title . "#32770AutoHotkey.exe") ; Using exe should be optional or possibly, check if compiled.
			return
		SetWinDelay,-1
		if (p.x!="")
			WinMove, % "ahk_id" lParam,, 	p.x
		if (p.y!="")
			WinMove, % "ahk_id" lParam,,, 	p.y
		if (p.w!="")
			WinMove, % "ahk_id" lParam,,,, 	p.w
		if (p.h!="")
			WinMove, % "ahk_id" lParam,,,,, p.h
		if p.fixed
			hMenu:=DllCall("user32\GetSystemMenu", "ptr", lParam, "int", false, "ptr"), DllCall("user32\RemoveMenu", "ptr", hMenu, "uint", 0xf010, "uint", 0) ;  due to jNizM @ https://autohotkey.com/boards/viewtopic.php?f=5&t=29549
		p:=""
		return OnMessage(id) ; Turn off message monitoring
	}
	p:=pos
	p.title:= title!="" ? title : A_ScriptName
	OnMessage(id, "MsgBoxPos")				; Turn on message monitoring
	MsgBox, % Options, % Title, % Text, % Timeout
	IfMsgBox Yes
		return "Yes"
	; add ifmsgbox x return "x"
	return "Not finished"
}
If you want the msgbox to be owned, I guess you could make it owned in the OnMessage block.

Code: Select all

if (hwnd==A_ScriptHwnd) {
; Make it owned here. The hook is not called for owned windows.
}

Re: Disable Moving a MsgBox

Posted: 24 Mar 2017, 13:20
by qwerty12
Hey Helgef,

I like your approach, with the Shell hook and everything encapsulated into one function :thumbup:

Re: Disable Moving a MsgBox

Posted: 24 Mar 2017, 13:53
by Helgef
qwerty12 wrote:Hey Helgef,

I like your approach, with the Shell hook and everything encapsulated into one function :thumbup:
Hey :)
The encapsulation is a little messy I'd say, but I think it is nice to have a single ready to use function for a simple task like this. Basically, I make it like this for those who don't want to write it themselves, they can use it and never mind the mess, if they get problems they'll leave a complaint to the author, which then has to sort it out. Now, since I just shared it in a conceptual state, any complaints are dismissed :D

If someone wants to finish it please feel free :thumbup:

Re: Disable Moving a MsgBox

Posted: 24 Mar 2017, 14:32
by qwerty12
Helgef wrote:The encapsulation is a little messy I'd say, but I think it is nice to have a single ready to use function for a simple task like this. Basically, I make it like this for those who don't want to write it themselves, they can use it and never mind the mess, if they get problems they'll leave a complaint to the author, which then has to sort it out. Now, since I just shared it in a conceptual state, any complaints are dismissed :D
:D I can see where you're coming from - for me, it's why I mostly post here instead of Scripts & Functions, as in my case I'm not really good at generalising code, but if I somehow know enough to actually propose an answer to a question, any code I post is tailored for that user and their request... But yeah, this is one of the more fun threads I've been in, with quite a few approaches to the problem :-)