An attempt at a script that creates MsgBoxes/InputBoxes for other scripts

Get help with using AutoHotkey and its commands and hotkeys
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

An attempt at a script that creates MsgBoxes/InputBoxes for other scripts

16 Mar 2017, 16:50

I am trying to create a script that creates GUIs for other scripts.
The idea is that all scripts can make use of a function that calls the GUI factory script.
Satellite scripts communicate with the 'GUI factory' script via an object.

The benefits are that scripts calling the function do not have to 'look after' the GUIs in any way and do not become Persistent, they just use one line to call the function. This would also make it easy to create special GUIs with hyperlinks, or multiple edit fields, or multiline edit fields, or with custom colours/fonts etc.

The issue is if multiple GUIs need to exist simultaneously, for one script or for multiple scripts, if anybody is able to work out a solution for this. I'm basically stuck on this. I hope others see potential value in this idea.

Communicating between scripts via an object is based on:
ObjRegisterActive - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?t=6148
Btw is this object multidimensional? I did a test which suggested it isn't, which meant the script had to be rewritten slightly.

Run the factory script first then (multiple copies of) the satellite script.

Code: Select all

;GUI factory script, creates GUIs

#SingleInstance force
#Persistent

;ObjRegisterActive - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?t=6148
; Register our object so that other scripts can get to it.  The second
; parameter is a GUID which I generated.  You should generate one unique
; to your script.  You can use [CreateGUID](http://goo.gl/obfmDc).

vGUID := "{6B39CAA1-A320-4CB0-8DB4-352AA81E460E}"
ObjRegisterActive(ActiveObject, vGUID)
OnExit("Revoke")
Return

Revoke()
{
ObjRegisterActive(ActiveObject, "")
ExitApp
}

class ActiveObject
{
	Message(oArray)
	{
	global
	DetectHiddenWindows, Off
	;MsgBox % oArray.ID
	vID := oArray.ID
	vIDGui%vID% := oArray.ID
	vDefault%vID% := oArray.InputDefault
	vPrompt%vID% := oArray.Prompt
	vWinTitle%vID% := oArray.WinTitle
	vType%vID% := oArray.Type
	vFontName%vID% := oArray.FontName
	vFontSize%vID% := oArray.FontSize
	;MsgBox % vPrompt%vID%
	vWait%vID% := 1

        Gui, New, % "+HwndhWnd" vID
	Gui, Gui%vID%:Font, % "s " vFontSize%vID%, % vFontName%vID%
	Gui, Gui%vID%:Color, F0F0F0
	Gui, Gui%vID%:+0x94CC0A4C +E0x00010100 +LabelGui
	Gui, Gui%vID%:Add, Text, x0 y0 w300 h100, % vPrompt%vID%
	if InStr(vType%vID%, "i")
		Gui, Gui%vID%:Add, Edit, x0 y100 w300 h47 vvEditGui%vID%, % vDefault%vID%
	Gui, Gui%vID%:Add, Button, x5 y170 w140 h40 Default gButtonOK, OK
	Gui, Gui%vID%:Add, Button, x155 y170 w140 h40 gButtonCancel, Cancel
	Gui, Gui%vID%:Show, w300 h230, % vWinTitle%vID%
	;MsgBox % hWnd%vID%
	;WinWaitClose, % "ahk_id " hWnd%vID%
	while vWait%vID%
	Sleep 10
	Return

	ButtonOK:
	ButtonCancel:
	GuiClose:
	GuiEscape:
	Gui, %A_Gui%:Submit
	Gui, %A_Gui%:Destroy
	;this["RetButton" A_Gui] := ""
	if (A_ThisLabel = "ButtonOK")
		this["RetButton" A_Gui] := "OK"
	if (A_ThisLabel = "ButtonCancel")
		this["RetButton" A_Gui] := "Cancel"
	if (A_ThisLabel = "GuiClose") || (A_ThisLabel = "GuiEscape")
		this["RetClose" A_Gui] := 1
	else
		this["RetClose" A_Gui] := 0

	;MsgBox % "RetInput" A_Gui
	;MsgBox % vEdit%A_Gui%
	this["RetInput" A_Gui] := vEdit%A_Gui%
	vWait%vID% := 0
	Return
	}
}

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

ObjRegisterActive(Object, CLSID, Flags:=0) {
    static cookieJar := {}
    if (!CLSID) {
        if (cookie := cookieJar.Remove(Object)) != ""
            DllCall("oleaut32\RevokeActiveObject", "uint", cookie, "ptr", 0)
        return
    }
    if cookieJar[Object]
        throw Exception("Object is already registered", -1)
    VarSetCapacity(_clsid, 16, 0)
    if (hr := DllCall("ole32\CLSIDFromString", "wstr", CLSID, "ptr", &_clsid)) < 0
        throw Exception("Invalid CLSID", -1, CLSID)
    hr := DllCall("oleaut32\RegisterActiveObject"
        , "ptr", &Object, "ptr", &_clsid, "uint", Flags, "uint*", cookie
        , "uint")
    if hr < 0
        throw Exception(format("Error 0x{:x}", hr), -1)
    cookieJar[Object] := cookie
}

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

Code: Select all

;GUI satellite script, asks for GUIs to be created

#SingleInstance force

;ObjRegisterActive - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?t=6148

;MsgBox
;https://autohotkey.com/docs/commands/MsgBox.htm
;InputBox
;https://autohotkey.com/docs/commands/InputBox.htm

;MsgBox
;https://lexikos.github.io/v2/docs/commands/MsgBox.htm
;InputBox
;https://lexikos.github.io/v2/docs/commands/InputBox.htm

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

vGUID := "{6B39CAA1-A320-4CB0-8DB4-352AA81E460E}"
oJEENfy := ComObjActive(vGUID)
;Return

;q::
SplitPath, A_ScriptName, , , , vWinTitle
vType := "i" ;an InputBox
vCount++ ;the nth GUI box created by the script
vID := (A_ScriptHwnd+0) "_" vType vCount

oArray := {}
oArray.ID := vID
oArray.WinTitle := vWinTitle
oArray.Type := vType
oArray.FontName := "Arial"
oArray.FontSize := 18
oArray.Prompt := "`r`nhello world`r`nbox " vCount
oArray.InputDefault := "default text"

oArray.RetButton := ""
oArray.RetClose := 0
oArray.RetInput := ""

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

oJEENfy.Message(oArray)
vInfo := "button chosen: " oJEENfy["RetButtonGui" vID] "`r`n"
vInfo .= "was window closed: " oJEENfy["RetCloseGui" vID] "`r`n"
vInfo .= "user input text: " oJEENfy["RetInputGui" vID]
MsgBox % vInfo
Return

;==================================================
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
4GForce
Posts: 553
Joined: 25 Jan 2017, 03:18
GitHub: 4GForce

Re: An attempt at a script that creates MsgBoxes/InputBoxes for other scripts

16 Mar 2017, 17:35

1- Why not have an object that you can include and instantiate ... having 2 scripts running doesn't seem like a good solution.
2- Labels inside a function ... eurk :sick: ... this is what is breaking your code flow and preventing multiple simultaneous guis.

Have a look at this, could give you ideas : https://autohotkey.com/boards/viewtopic ... 57#p136157
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: An attempt at a script that creates MsgBoxes/InputBoxes for other scripts

16 Mar 2017, 19:47

@4GForce Hahaha 'eurk'.

What I am trying to do is create a function that has all the advantages of a standard AHK command like MsgBox/InputBox. In AHK v1, Gui commands have the severe disadvantage of making a script '#Persistent', that's why the satellite scripts must call a separate script. (No library function used by a script should make a script become #Persistent, or have other undesirable effects on a script, that a script didn't ask for.)

I tried moving out the labels from the function. Again it works for individual MsgBox requests. But again it doesn't work if I have two scripts asking for GUIs at the same time.

Potentially almost all custom functions that create GUIs, have failed to account for having two GUIs at the same time.

Code: Select all

;GUI factory script, creates GUIs

#SingleInstance force
#Persistent

;ObjRegisterActive - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?t=6148

; Register our object so that other scripts can get to it.  The second
; parameter is a GUID which I generated.  You should generate one unique
; to your script.  You can use [CreateGUID](http://goo.gl/obfmDc).
vGUID := "{6B39CAA1-A320-4CB0-8DB4-352AA81E460E}"
ObjRegisterActive(ActiveObject, vGUID)
OnExit("Revoke")
Return

Revoke()
{
ObjRegisterActive(ActiveObject, "")
ExitApp
}

class ActiveObject
{
	Message(oArray)
	{
	global
	DetectHiddenWindows, Off
	;MsgBox % oArray.ID
	vID := oArray.ID
	vIDGui%vID% := oArray.ID
	vDefault%vID% := oArray.InputDefault
	vPrompt%vID% := oArray.Prompt
	vWinTitle%vID% := oArray.WinTitle
	vType%vID% := oArray.Type
	vFontName%vID% := oArray.FontName
	vFontSize%vID% := oArray.FontSize
	;MsgBox % vPrompt%vID%
	vWait%vID% := 1

        Gui, New, % "+HwndhWnd" vID
	Gui, Gui%vID%:Font, % "s " vFontSize%vID%, % vFontName%vID%
	Gui, Gui%vID%:Color, F0F0F0
	Gui, Gui%vID%:+0x94CC0A4C +E0x00010100 +LabelGui
	Gui, Gui%vID%:Add, Text, x0 y0 w300 h100, % vPrompt%vID%
	if InStr(vType%vID%, "i")
		Gui, Gui%vID%:Add, Edit, x0 y100 w300 h47 vvEditGui%vID%, % vDefault%vID%
	Gui, Gui%vID%:Add, Button, x5 y170 w140 h40 Default gButtonOK, OK
	Gui, Gui%vID%:Add, Button, x155 y170 w140 h40 gButtonCancel, Cancel
	Gui, Gui%vID%:Show, w300 h230, % vWinTitle%vID%
	;MsgBox % hWnd%vID%
	;WinWaitClose, % "ahk_id " hWnd%vID%
	while vWait%vID%
	Sleep 10
	this["RetButtonGUI" vID] := vRetButtonGui%vID%
	this["RetCloseGUI" vID] := vRetCloseGui%vID%
	this["RetInputGUI" vID] := vEditGui%vID%
	Return
	}
}

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

ButtonOK:
ButtonCancel:
GuiClose:
GuiEscape:
Gui, %A_Gui%:Submit
Gui, %A_Gui%:Destroy
;vRetButton%A_Gui% := ""
if (A_ThisLabel = "ButtonOK")
	vRetButton%A_Gui% := "OK"
if (A_ThisLabel = "ButtonCancel")
	vRetButton%A_Gui% := "Cancel"
if (A_ThisLabel = "GuiClose") || (A_ThisLabel = "GuiEscape")
	vRetClose%A_Gui% := 1
else
	vRetClose%A_Gui% := 0
vWait%vID% := 0
Return

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

ObjRegisterActive(Object, CLSID, Flags:=0) {
    static cookieJar := {}
    if (!CLSID) {
        if (cookie := cookieJar.Remove(Object)) != ""
            DllCall("oleaut32\RevokeActiveObject", "uint", cookie, "ptr", 0)
        return
    }
    if cookieJar[Object]
        throw Exception("Object is already registered", -1)
    VarSetCapacity(_clsid, 16, 0)
    if (hr := DllCall("ole32\CLSIDFromString", "wstr", CLSID, "ptr", &_clsid)) < 0
        throw Exception("Invalid CLSID", -1, CLSID)
    hr := DllCall("oleaut32\RegisterActiveObject"
        , "ptr", &Object, "ptr", &_clsid, "uint", Flags, "uint*", cookie
        , "uint")
    if hr < 0
        throw Exception(format("Error 0x{:x}", hr), -1)
    cookieJar[Object] := cookie
}

;==================================================
This may relate to a more a fundamental problem, see this simple example:

I may be missing something quite simple, but the issue seems so fundamental, I would have thought someone might be able to explain what the problem is, and if there's a workaround.

Code: Select all

;you can't close a/b/c until you close d
;(once d is closed, you can't close a/b until you close c)
;(once c/d are closed, you can't close a until you close b)

#Persistent
#SingleInstance force

SetTimer, Timer1, -500
SetTimer, Timer2, -1000
SetTimer, Timer3, -1500
SetTimer, Timer4, -2000
Return

Timer1:
MsgBox, % "", % "a", % "a"
vLog .= "a"
ToolTip % vLog
Return

Timer2:
MsgBox, % "", % "b", % "b"
vLog .= "b"
ToolTip % vLog
Return

Timer3:
MsgBox, % "", % "c", % "c"
vLog .= "c"
ToolTip % vLog
Return

Timer4:
MsgBox, % "", % "d", % "d"
vLog .= "d"
ToolTip % vLog
Return
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: An attempt at a script that creates MsgBoxes/InputBoxes for other scripts

16 Mar 2017, 20:47

Another point of interest:

Code: Select all

;the order of vLog is always d c b a
;even if you close one/some of the windows in a different order (via the close button, or 'alt+space, close')

#Persistent
#SingleInstance force

SetTimer, TimerA, -500
SetTimer, TimerB, -1000
SetTimer, TimerC, -1500
SetTimer, TimerD, -2000
Return

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

TimerA:
TimerB:
TimerC:
TimerD:
InputBox, vInput%A_ThisLabel%, % A_ThisLabel, % A_ThisLabel, , , , , , , , % A_ThisLabel
vLog .= vInput%A_ThisLabel% " "
ToolTip % vLog
if (StrLen(vLog) = 28)
	MsgBox % vLog
Return

;==================================================
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
4GForce
Posts: 553
Joined: 25 Jan 2017, 03:18
GitHub: 4GForce

Re: An attempt at a script that creates MsgBoxes/InputBoxes for other scripts

17 Mar 2017, 01:06

jeeswg wrote:In AHK v1, Gui commands have the severe disadvantage of making a script '#Persistent'
I get that, but every script should have an exit point, as for msgbox they are blocking the code flow anyway so ... yeah ...
jeeswg wrote:I tried moving out the labels from the function.
The day you forget about labels and start thinking about functions ... you will see the light !
Don't just move them out, use function instead !? Have you even looked at what I linked ?
Labels are sooooo two thousand and late :crazy:
Then again, I couldn't get it to work within 20 minutes and I'm lazy, not sure if it has to do with AHK thread limitations.
This gets multiple gui up but Gui A wont respond until Gui B is answered ...

Code: Select all

#singleinstance force

F10::
ExitApp

F9::Reload

F1::
	mb1 := new CustomMsgBox("A", "MB1")
	response := mb1.Display()
	if(response == CustomMsgBox.Btn_Yes) {
		msgbox % "User clicked yes"
	}
	else if(response == CustomMsgBox.Btn_No) {
		msgbox % "User clicked no"
	}
Return

F2::
	mb2 := new CustomMsgBox("B", "MB2")
	response := mb2.Display()
	if(response == CustomMsgBox.Btn_Yes) {
		msgbox % "User clicked yes"
	}
	else if(response == CustomMsgBox.Btn_No) {
		msgbox % "User clicked no"
	}
Return

Class CustomMsgBox {
	static Btn_Yes := "Yes"
	static Btn_No := "No"
	
	__new(gid, title) {
		this.Gid := gid
		this.Title := title
		this.Result := False
	}
	
	Display() {
		this.Create()
		this.Result := ""
		loop
		{
			sleep 1
		} until (this.Result)
		gid := this.Gid
		Gui %gid%:Destroy
		Return this.Result
	}
	
	Create() {
		gid := this.Gid
		Gui, %gid%:New
		Gui %gid%:Add, Button, hwndMyButton, % CustomMsgBox.Btn_Yes
		fn := this.ButtonClicked.Bind(this)
		GuiControl +g, % MyButton, % fn
		Gui %gid%:Add, Button, hwndMyButton, % CustomMsgBox.Btn_No
		GuiControl +g, % MyButton, % fn
		Gui %gid%:Show, w200 h200, % this.Title
	}
	
	ButtonClicked() {
		this.Result := A_GuiControl
	}	
}
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: An attempt at a script that creates MsgBoxes/InputBoxes for other scripts

20 Mar 2017, 04:46

Further tests exploring the difficulty of successfully replacing/replicating MsgBox/InputBox/ToolTip/Progress/SplashImage, while maintaining all aspects of functionality.

(I.e. custom MsgBox/InputBox/ToolTip replacements with optional custom font, and potential other features, and which don't cause #Persistent.)

(I.e. Progress/SplashImage are scheduled to be removed in AutoHotkey v2.)

Some further tests relating to '#MaxThreads', 'Critical' and 'Thread, Priority' together with 'SetTimer' and 'OnMessage'.

The script resizes (and changes the fonts of) an InputBox, immediately after creating it.

It also has example code for recreating the AHK v2 InputBox function as an AHK v1 function.

Code: Select all

#SingleInstance force
;#MaxThreads 1 ;blocks SetTimer and MsgMonitor
OnMessage(0x5555, "MsgMonitor")
;note: try sending 0x5555 to this script while an InputBox is shown
;PostMessage, 0x5555, 0, 0, , % "ahk_id " hWnd
Return

q:: ;modify an InputBox to have a custom font on creation
;OutputVar := InputBoxCustom("text", "title", "x100 y100 w300 h300 t5 password*", "default")
OutputVar := InputBoxCustom("text", "title", "w300 h300", "default")
;MsgBox % OutputVar

Critical ;does not block SetTimer or MsgMonitor
OutputVar := InputBoxCustom("text", "title", "w300 h300", "default")
;Func1()

Thread, Priority, 2 ;blocks SetTimer and MsgMonitor (OnMessage)
;note: SetTimer is needed for the InputBox resize 'hack'
;note: OnMessage is needed for GUIs (made by the Gui command)
;note: OnMessage is needed for GUIs (made by DllCalls)
OutputVar := InputBoxCustom("text", "title", "w300 h300", "default")
;Func1()
Return

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

;parameters based on AHK v2
;note in AHK v1 the password character must be *
InputBoxCustom(Text="", Title="", Options="", Default="")
{
Options := StrReplace(Options, "`t", " ")
Loop, Parse, Options, % " "
{
	Char := SubStr(A_LoopField, 1, 1)
	(Char = "x") ? (X := SubStr(A_LoopField, 2)) : ""
	(Char = "y") ? (Y := SubStr(A_LoopField, 2)) : ""
	(Char = "w") ? (Width := SubStr(A_LoopField, 2)) : ""
	(Char = "h") ? (Height := SubStr(A_LoopField, 2)) : ""
	(Char = "t") ? (Timeout := SubStr(A_LoopField, 2)) : ""
	(SubStr(A_LoopField, 1, 8) = "Password") ? (Hide := "HIDE") : ""
}
SetTimer, InputBoxResize, 10
InputBox, v, % Title, % Text, % Hide, % Width, % Height, % X, % Y, , % Timeout, % Default
Return v
}

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

InputBoxResize:
SetTimer, InputBoxResize, Off
WinGet, hWnd, ID, A
WinGet, vPID, PID, % "ahk_id " hWnd
vPIDAhk := DllCall("kernel32\GetCurrentProcessId")
if !(vPID = vPIDAhk)
	Return

;include this line to see the transition from normal to large
;Sleep 2000

;include this line to enable word wrap
;Control, Style, +0x2000, Static1, % "ahk_id " hWnd

hFont := JEE_FontCreate("Arial", 18, "b")
PostMessage, 0x30, % hFont, 0, Static1, % "ahk_id " hWnd ;WM_SETFONT
PostMessage, 0x30, % hFont, 0, Edit1, % "ahk_id " hWnd ;WM_SETFONT

vZoom := 1.5
CoordMode, Pixel, Screen

WinGetPos, vPosX, vPosY, vPosW, vPosH, % "ahk_id " hWnd
vPosW := Round(vPosW * vZoom), vPosH := Round(vPosH * vZoom)
;the line below makes the InputBox central
vPosX := Round(A_ScreenWidth/2) - Round(vPosW/2), vPosY := Round(A_ScreenHeight/2) - Round(vPosH/2) ;to centre window
WinMove, % "ahk_id " hWnd, , % vPosX, % vPosY, % vPosW, % vPosH

ControlGetPos, vPosX, vPosY, vPosW, vPosH, Edit1, % "ahk_id " hWnd
ControlMove, Edit1, , % vPosY-20, , % vPosH * vZoom, % "ahk_id " hWnd
ControlGetPos, vPosX, vPosY, vPosW, vPosH, Static1, % "ahk_id " hWnd
ControlMove, Static1, , , , % vPosH * vZoom, % "ahk_id " hWnd

ControlGet, hCtl3, Hwnd, , Edit1, % "ahk_id " hWnd
WinSet, Redraw, , % "ahk_id " hCtl3
ControlGet, hCtl1, Hwnd, , Button1, % "ahk_id " hWnd
WinSet, Redraw, , % "ahk_id " hCtl1
ControlGet, hCtl2, Hwnd, , Button2, % "ahk_id " hWnd
WinSet, Redraw, , % "ahk_id " hCtl2
Return

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

Func1()
{
MsgBox % 1
Func2()
}

Func2()
{
MsgBox % 2
Func3()
}

Func3()
{
MsgBox % 3
}

MsgMonitor(wParam, lParam, uMsg, hWnd)
{
MsgBox % "m"
}

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

;e.g. hFont := JEE_FontCreate("Arial", 12, "bius")

;JEE_CreateFont
JEE_FontCreate(vName, vSize, vFontStyle="")
{
vHeight := -DllCall("kernel32\MulDiv", Int,vSize, Int,A_ScreenDPI, Int,72)
vWidth := 0
vEscapement := 0
vOrientation := 0
vWeight := InStr(vFontStyle, "b") ? 700 : 400
vItalic := InStr(vFontStyle, "i") ? 1 : 0
vUnderline := InStr(vFontStyle, "u") ? 1 : 0
vStrikeOut := InStr(vFontStyle, "s") ? 1 : 0
vCharSet := 0
vOutPrecision := 0
vClipPrecision := 0
vQuality := 0
vPitchAndFamily := 0
vFaceName := vName

vOutPrecision := 3
vClipPrecision := 2
vQuality := 1
vPitchAndFamily := 34

hFont := DllCall("CreateFont", "int", vHeight, "int", vWidth, "int", vEscapement, "int", vOrientation
, "int", vWeight, "Uint", vItalic, "Uint", vUnderline, "uint", vStrikeOut
, "Uint", vCharSet, "Uint", vOutPrecision, "Uint", vClipPrecision, "Uint", vQuality
, "Uint", vPitchAndFamily, "str", vFaceName, "Ptr")
Return hFont
}

;==================================================
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Ask For Help”

Who is online

Users browsing this forum: Ianizer, mikeyww, mrdigitalis, teadrinker and 65 guests