Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Class: InputBoxEx - Customizable InputBox


  • Please log in to reply
8 replies to this topic
Coco
  • Members
  • 697 posts
  • Last active: Oct 31 2015 07:26 PM
  • Joined: 27 Jul 2012

DOWNLOAD: https://dl.dropbox.com/u/48290125/InputBoxEx.ahk
 
CLASS: InputBoxEx
DESCRIPTION: Provides users an easy way to create customized/dynamic InputBoxes.
REQUIREMENT(s): AutoHotkey v. 1.1.9.x (I'm using the ":=" operator for optional parameters in functions/methods)
FEATURES: All options available in the standard AHK InputBox+more(see below)
- Option to create multiple input fields
- GUI can be customized
- Each input field can be customized
  * prompt
  * font[name,color,size,style]
  * no. of rows[single or multiline]
  * input type[numbers only]
  * display type[password, uppercase, lowercase]
  * attach tooltip
  * set cue banner
  * add button(s) that allow users to select a file or a folder

- Option to remove or show the "Cancel" button
- Option to associate a label or a function with a particular event.
  Event types:
   * OK - User clicked the OK button
   * Cancel - User clicked the Cancel button
   * Close - User dismissed the InputBox via the Close button
   * Timeout - InputBox timed out

- More features are to be added

USAGE: Script designer need only call two methods
- Use #Include InputBoxEx.ahk or #Include <InputBoxEx> (if script is located in your lib folder) or copy code and paste it on your script
- Instantiate the class using the "new" keyword and pass the appropriate parameter(s)
  e.g. ibxObj := new InputBoxEx(params*) -for parameters, see documentation for __New() method-
- Then show the InputBox using the Show() method
  e.g. ibxObj.Show(param) -for parameters, see documentation for Show() method-

NOTES:
Behavior:
  - It behaves pretty much the same as the standard AHK InputBox.
  - When the user calls the Show() method, the lines below it will not be executed until the InputBox is dismissed (via OK, Cancel, Close or Timeout). ***similar to MsgBox and AHK InputBox
  - If an associated action(label or function) is assigned to an event, that subroutine will be executed first then script will continue at the lines(if any) after the Show() method.
  - Text(output) that the user has input can be retieved via the ibxObj.Output[x](which is an object) property, where x is the index of a particualr input field.
   e.g. Field1 := ibxObj.Output.1 (or ibxObj.Output[1]) and so on...
  - When the user dismisses the InputBox, ErrorLevel is set to either of the following values(depending on the event): "OK", "Cancel", "Close", "Timeout"
   *** if an event has an associated action(laber or function), and if within that subroutine ErrorLevel's value is altered by a command(e.g. Run), upon return from that subroutine,
   ErrorLevel is reset back to the "event" type. This allows users to retrieve the event type via ErrorLevel on the lines after the Show() method call.
  - When associating a function with an event, the function should have atleast 1 parameter & the minimum required parameter(s) should be equal to or less than 1.
   *** The class passes an array/object to the function's 1st parameter which contains whatever the user has input.
   *** The associated function's return value (if any) is returned by the Show() method. e.g.: IBX_EventRetVal := ibxObj.Show(param)

Others/ToDo's/Issues:
  - [issue] Currently, InputBoxEx is not manually resizable (will work on this)
  - [issue] Button images might appear different on other systems. Tested only on Win7, Dll files have different icon nos on different systems (will fix this)
  - [intentional] InputBoxEx height is not configurable, this is done automatically.
  - [intentional] Unlike input field(s) whose font can be set individually, "prompt" font is uniform and follows the whatever is set by the user for the GUI font (might allow customization)
  - [todo] Implement option to allow users to add a ComboBox and/or a DateTime control instead of just an Edit control as an input field.
  - [todo] Option to add an UpDown control and use the input field as a buddy control.
  - [todo?] Might add option to allow users to add Checkboxes and Radio controls
  - [todo?] Option to add custom buttons with custom actions

CREDITS:
 - Credits to tkoi and corrupt for the ILButton() function. (http://www.autohotke...ates-alignment/)
 - Credits to sbc for the GetBorderSize() function. (http://www.autohotke...-getbordersize/)
 - Credits to art for AddToolTip() function, I altered it a bit though. (http://www.autohotke...e-2#entry431059)

 

SCREENSHOT

InputBoxEx.png



Coco
  • Members
  • 697 posts
  • Last active: Oct 31 2015 07:26 PM
  • Joined: 27 Jul 2012

METHODS:
NAME: __New()
DESCRIPTION: Constructor for the class
PARAMETER(s):
  prompt(required) - Text to display, a message to the user indicating what kind of input is expected. User can pass a string or an object/array. If string, it can be a pipe-delimited list of text items to display, no. of items indicates the no. of input field(s).

Example: (String) - "Prompt One|Prompt Two|Prompt Three" ; (Object/Array) - ["Prompt One", Two" , Three"]

 

  title(optional) - Title of the InputBox, If blank or omitted, it defaults to the name of the script.

 

  optionsA(optional) - InputBox GUI options. String containing any(or none) of following(space-delimited). If blank, default settings will be used(see description below).

Example: "w450 c0xFFFFFF Font(Segoe UI|s9 cRed)"

        Wn - Width of the InputBox, where n is the amount in pixels. If omitted, it defaults to 375(same with AHK standard InputBox
        C - GUI color. Specify the letter "C" followed immediately by a color name(see color chart in AHK help file) or RGB value (the 0x prefix is optional).
        Font(FontName|FontOptions) or F(FontName|FontOptions) - GUI font. Specify the word "Font" or the letter "F" (not case-sensitive) followed by immediately by a string enclosed in parentheses(open and close). String format should be: '(Font Name|Font Options)' (note pipe delimiter and parentheses), where "Font Name" is the name of the font and "Font Options" is a space-delimited string containing standard font options(see AHK "Gui, Font" command for a list of options. e.g.: "cRed Italic s9")

 Example: Font(Segoe UI|cRed s10 Italic Underline)

        -Cancel - Specify the word "-Cancel" to remove the "Cancel" button. InputBox will only have the "OK" button if this option is specified. Default is two buttons: OK and Cancel

 

  action(optional) - Label or Funcion to call for each InputBoxEx event. There are 4 events: OK, Cancel, Close, Timeout. (Self-explanatory). User can pass a string or an object/array. If string, it should be a pipe-delimited list(max=4 items) of labels or functions names where item1 corresponds to the "OK" event, item2 to the "Cancel" event, item3 to the "Close" event and item4 to the "Timeout" event. If no label or function is specified for a particular event , no action is taken. When InputBoxEx is dismissed via any of the events stated above, user can retrieve the event type/name via the obj.Event property. (This is similar to ErrorLevel for the standard AHK InputBox)

Example: "LabelOK|LabelCancel|LabelClose|LabelTimeout" or if Array/object: ["LabelOK", "LabelCancel", "LabelClose", "LabelTimeout"] 


  optionsB(variadic) - InputBox input field option(s). String containing any(or none) of following(space-delimited). If blank, default settings will be used(see description below). Number of parameters must be equal or less than the number of items in the "prompt" parameter. Each parameter corresponds to each prompt/field.
Example: "R5 Default(This is the default text) Folder(C:\Windows\System32) Font(Consolas|s9 cBlue Italic) File(C:\Users\Username\Pictures|Image Files:*.jpg; *.png; *.bmp)"
        R - Number of rows of the input field(Edit control). Specify the letter "R" followed immediately by a number. If omitted it defaults to 1. (e.g.: R5 - five rows)
        Password - Hides the user's input. Similar to "Hide" option for the standard AHK InputBox. This option has no effect for multi-line input fields
        Number - Prevents the user from typing anything other than digits into the field.
        Uppercase or Upper - The characters typed by the user are automatically converted to uppercase.
        Lowercase or Lower - The characters typed by the user are automatically converted to lowercase.
        -Wrap - Turns off word-wrapping in a multi-line input field.
        HScroll - Adds a horizontal scrollbar, useful for input fields with the "-Wrap" option.
        Font(FontName|FontOptions) or F(FontName|FontOptions) - Input field font. For usage/format, see same option for "optionsA" parameter above.
        Default(DefaultText) - A string that will appear in the InputBox's edit field when the dialog first appears. Specify the word "Default" followed immediately by a string enclosed in parentheses(open and close)

Example: Default(This is the default text.)

        Cue(CueText) - Sets the textual cue, or tip, that is displayed by the input field to prompt the user for information. Specify the word "Cue" followed immediately by a string enclosed in parentheses(open and close). This option has no effect for multi-line edit controls.

Example: Cue(This is a cue banner)

        Tip(TipText) - Sets/creates the tooltip that is displayed every time the mouse hovers over the input field/edit control. Specify the word "Tip" followed immediately by a string enclosed in parentheses(open and close).

Example: Tip(This is a tooltip)

        File(RootDir|Filter) - Adds a button to the right of the input field that when pressed, displays a standard dialog that allows the user to select a file. Specify the word "File" followed immediately by a string enclosed in parentheses(open and close). String format should be: '(RootDir|Filter)' (note pipe delimiter and parentheses), where RootDir is the root(starting) directory and filter indicates which types of files are shown by the dialog. If 'RootDir' is omitted (e.g. by just specifying 'File(|Filter)', **note a pipe "|" is still specified or else 'Filter' will be assumed as 'RootDir'**) it defaults to the user's "My Documents" folder. The button is a 25x25 px sized button with a "search folder" icon. A tooltip with the text "Browse file" is displayed when the mouse hovers over the button. ***FILTER SYNTAX: [FileType:*FileExt1; *FileExt2] (note colon[:] delimiter between "FileType" and "FileExt" and also the semi-colon[;]+space that separates each file extension). If omitted, the filter defaults to All Files (*.*).

Example: File(C:\Windows|Audio:*.wav; *.mp2; *.mp3)

        Folder(RootDir) - Adds a button to the right of the input field that when pressed, displays a standard dialog that allows the user to select a folder. Path is displayed in the input field. Specify the word "Folder" followed immediately by a string enclosed in parentheses(open and close). String format should be: '(RootDir)' (note parentheses) , where RootDir is the root(starting) directory. If 'RootDir' is omitted (e.g. by just specifying 'Folder()') it defaults to the user's "My Documents" folder. The button is a 25x25 px sized button with a "search folder" icon. A tooltip with the text "Browse folder" is displayed when the mouse hovers over the button.

Example: Folder(C:\Windows)


USAGE: Use the "new" keyword to create a class instance
    Syntax:

ibxObj := new InputBoxEx(prompt, title, optionsA, action, optionsB*)

    Example:

ibxObj := new InputBoxEx("Name:|Address:", "Contact Details", "w400 cWhite Font(Tahoma|s9 cMaroon)", "OK|Cancel|Close|Timeout", "Cue(Enter name) Font(Segoe UI|s9)", "R3 Tip(Address here) Font(Segoe UI|s9)")

 

RETURN VALUE: A derived object

 
NAME: Show()
DESCRIPTION: Shows the InputBox
PARAMATER(s):
  options(optional) - String containing any(or none) of following(space-delimited)
      xn - x-position where n is the amount in pixels
      yn - y-position where n is the amount in pixels
      tn - timeout , where n is the amount in milliseconds

USAGE:

ibxObj.Show("x10 y50 t5000")

 

RETURN VALUE: If an event has an associated action, particularly a function, this method returns that function's return value(if any) otherwise none



MikeV
  • Members
  • 19 posts
  • Last active: Sep 13 2015 09:08 PM
  • Joined: 03 Jun 2013

I just started to use your InputBoxEx and found a few issues. I have enabled warnings in my script via #Warn. When I do this, AHK issues many warnings before the input box is shown and then a few more additional warnings after selecting either OK or Cancel.

Also, if I enter values into the fields, then click Cancel, the Output array still contains those values.

BTW, I think you did a great job in providing this functionality. Thank you. This control will allow me to add significant convenience to my script.

 

If anyone else has taken this script and resolved the warnings, please share your version.

Mike V.



MikeV
  • Members
  • 19 posts
  • Last active: Sep 13 2015 09:08 PM
  • Joined: 03 Jun 2013

I took a shot at resolving the warnings.  All of my edits can be found by searching for "MPV".

/*
CLASS: InputBoxEx
DESCRIPTION: Provides users an easy way to create customized/dynamic InputBoxes.
REQUIREMENT(s): AutoHotkey v. 1.1.9.x (I'm using the ":=" operator for optional parameters in functions/methods)
FEATURES: All options available in the standard AHK InputBox+more(see below)
	- Option to create multiple input fields
	- GUI can be customized [color and font]
	- Each input field can be customized
		* prompt
		* font[name,color,size,style]
		* no. of rows[single or multiline]
		* input type[numbers only]
		* display type[password, uppercase, lowercase]
		* attach tooltip
		* set cue banner
		* add button(s) that allow users to select a file or a folder
	- Option to remove or show the "Cancel" button
	- Option to associate a label or a function with a particular event.
		Event types:
			* OK - User clicked the OK button
			* Cancel - User clicked the Cancel button
			* Close - User dismissed the InputBox via the Close button
			* Timeout - InputBox timed out
	- More features are to be added
USAGE: Script designer need only call two methods
	- Use #Include InputBoxEx.ahk or #Include <InputBoxEx> (if script is located in your lib folder) or copy code and paste it on your script
	- Instantiate the class using the "new" keyword and pass the appropriate parameter(s)
		e.g. ibxObj := new InputBoxEx(params*) -for parameters, see documentation for __New() method-
	- Then show the InputBox using the Show() method
		e.g. ibxObj.Show(param) -for parameters, see documentation for Show() method-
NOTES:
	Behavior:
		- It behaves pretty much the same as the standard AHK InputBox.
		- When the user calls the Show() method, the lines below it will not be executed until the InputBox is dismissed (via OK, Cancel, Close or Timeout). ***similar to MsgBox and AHK InputBox
		- If an associated action(label or function) is assigned to an event, that subroutine will be executed first then script will continue at the lines(if any) after the Show() method.
		- Text(output) that the user has input can be retieved via the ibxObj.Output[x](which is an object) property, where x is the index of a particualr input field.
			e.g. Field1 := ibxObj.Output.1 (or ibxObj.Output[1]) and so on...
		- When the user dismisses the InputBox, ErrorLevel is set to either of the following values(depending on the event): "OK", "Cancel", "Close", "Timeout"
			*** if an event has an associated action(laber or function), and if within that subroutine ErrorLevel's value is altered by a command(e.g. Run), upon return from that subroutine,
			ErrorLevel is reset back to the "event" type. This allows users to retrieve the event type via ErrorLevel on the lines after the Show() method call.
		- When associating a function with an event, the function should have atleast 1 parameter & the minimum required parameter(s) should be equal to or less than 1.
			*** The class passes an array/object to the function's 1st parameter which contains whatever the user has input.
			*** The associated function's return value (if any) is returned by the Show() method. e.g.: IBX_EventRetVal := ibxObj.Show(param)
	Others/ToDo's/Issues:
		- [issue] Currently, InputBoxEx is not manually resizable (will work on this)
		- [issue] Button images might appear different on other systems. Tested only on Win7, Dll files have different icon nos on different systems (will fix this)
		- [intentional] InputBoxEx height is not configurable, this is done automatically.
		- [intentional] Unlike input field(s) whose font can be set individually, "prompt" font is uniform and follows the whatever is set by the user for the GUI font (might allow customization)
		- [todo] Implement option to allow users to add a ComboBox and/or a DateTime control instead of just an Edit control as an input field.
		- [todo] Option to add an UpDown control and use the input field as a buddy control.
		- [todo?] Might add option to allow users to add Checkboxes and Radio controls
		- [todo?] Option to add custom buttons with custom actions
CREDITS:
	Credits to tkoi and corrupt for the ILButton() function. (http://www.autohotkey.com/board/topic/37147-ilbutton-image-buttons-with-text-states-alignment/)
	Credits to sbc for the GetBorderSize() function. (http://www.autohotkey.com/board/topic/60410-function-getbordersize/)
	Credits to art for AddToolTip() function, I altered it a bit though. (http://www.autohotkey.com/board/topic/27670-add-tooltips-to-controls/page-2#entry431059)
*/

class InputBoxEx
{

	/*
	METHOD: __New()
	DESCRIPTION: Constructor for the class
	PARAMETER(s):
		prompt(required) - Text to display, a message to the user indicating what kind of input is expected
											User can pass a string or an object/array. If string, it can be a pipe-delimited list of text items to display, no. of items indicates the no. of input field(s)
											Example: (String) - "Prompt One|Prompt Two|Prompt Three" ; (Object/Array) - ["Prompt One", "Prompt Two" , "Prompt Three"]

		title(optional) - Title of the InputBox, If blank or omitted, it defaults to the name of the script.

		optionsA(optional) - InputBox GUI options. String containing any(or none) of following(space-delimited). If blank, default settings will be used(see description below)
											Example: "w450 c0xFFFFFF Font(Segoe UI|s9 cRed)"
			Wn - Width of the InputBox, where n is the amount in pixels. If omitted, it defaults to 375(same with AHK standard InputBox)
			C - GUI color. Specify the letter "C" followed immediately by a color name(see color chart in AHK help file) or RGB value (the 0x prefix is optional).
			Font(FontName|FontOptions) or F(FontName|FontOptions) - GUI font. Specify the word "Font" or the letter "F" (not case-sensitive) followed by immediately by a string enclosed in parentheses(open and close)
																														String format should be: '(Font Name|Font Options)' (note pipe delimiter and parentheses), where "Font Name" is the name of the font
									                                                                                    and "Font Options" is a space-delimited string containing standard font options(see AHK "Gui, Font" command for a list of options. e.g.: "cRed Italic s9")
																														Example: Font(Segoe UI|cRed s10 Italic Underline) ***note: this is a string not a function or expression, for easy parsing***
			-Cancel - Specify the word "-Cancel" to remove the "Cancel" button. InputBox will only have the "OK" button if this option is specified. Default is two buttons: OK and Cancel

		action(optional) - Label or Funcion to call for each InputBoxEx event. There are 4 events: OK, Cancel, Close, Timeout. (Self-explanatory)
										User can pass a string or an object/array. If string, it should be a pipe-delimited list(max=4 items) of labels or functions names where item1 corresponds to the "OK" event
										, item2 to the "Cancel" event, item3 to the "Close" event and item4 to the "Timeout" event. If no label or function is specified for a particular event , no action is taken.
										When InputBoxEx is dismissed via any of the events stated above, user can retrieve the event type/name via the obj.Event property. (This is similar to ErrorLevel for the standard AHK InputBox)
										Example: "LabelOK|LabelCancel|LabelClose|LabelTimeout" or if Array/object: ["LabelOK", "LabelCancel", "LabelClose", "LabelTimeout"] ***see notes reagrding InputBoxEx behavior***

		optionsB(variadic) - InputBox input field option(s). String containing any(or none) of following(space-delimited). If blank, default settings will be used(see description below).
											Number of parameters must be equal or less than the number of items in the "prompt" parameter. Each parameter corresponds to each prompt/field.
										    Example: "R5 Default(This is the default text) Folder(C:\Windows\System32) Font(Consolas|s9 cBlue Italic) File(C:\Users\Username\Pictures|Image Files:*.jpg; *.png; *.bmp)"
			R - Number of rows of the input field(Edit control). Specify the letter "R" followed immediately by a number. If omitted it defaults to 1. (e.g.: R5 - five rows)
			Password - Hides the user's input. Similar to "Hide" option for the standard AHK InputBox. This option has no effect for multi-line input fields
			Number - Prevents the user from typing anything other than digits into the field.
			Uppercase or Upper - The characters typed by the user are automatically converted to uppercase.
			Lowercase or Lower - The characters typed by the user are automatically converted to lowercase.
			-Wrap - Turns off word-wrapping in a multi-line input field.
			HScroll - Adds a horizontal scrollbar, useful for input fields with the "-Wrap" option.
			Font(FontName|FontOptions) or F(FontName|FontOptions) - Input field font. For usage/format, see same option for "optionsA" parameter above.
			Default(DefaultText) - A string that will appear in the InputBox's edit field when the dialog first appears.
													Specify the word "Default" followed immediately by a string enclosed in parentheses(open and close)
			                                        Example: Default(This is the default text.) ***note: this is a string not a function or expression, for easy parsing***
			Cue(CueText) - Sets the textual cue, or tip, that is displayed by the input field to prompt the user for information.
										Specify the word "Cue" followed immediately by a string enclosed in parentheses(open and close)
										Example: Cue(This is a cue banner) ***note: this is a string not a function or expression, for easy parsing***
										This option has no effect for multi-line edit controls.
			Tip(TipText) - Sets/creates the tooltip that is displayed every time the mouse hovers over the input field/edit control.
									Specify the word "Tip" followed immediately by a string enclosed in parentheses(open and close)
									Example: Tip(This is a tooltip) ***note: this is a string not a function or expression, for easy parsing***
			File(RootDir|Filter) - Adds a button to the right of the input field that when pressed, displays a standard dialog that allows the user to select a file.
												Specify the word "File" followed immediately by a string enclosed in parentheses(open and close).
												String format should be: '(RootDir|Filter)' (note pipe delimiter and parentheses), where RootDir is the root(starting) directory and filter indicates which types of files are shown by the dialog.
												If 'RootDir' is omitted (e.g. by just specifying 'File(|Filter)', **note a pipe "|" is still specified or else 'Filter' will be assumed as 'RootDir'**) it defaults to the user's "My Documents" folder
												The button is a 25x25 px sized button with a "search folder" icon. A tooltip with the text "Browse file" is displayed when the mouse hovers over the button.
												***FILTER SYNTAX: [FileType:*FileExt1; *FileExt2] (note colon[:] delimiter between "FileType" and "FileExt" and also the semi-colon[;]+space that separates each file extension). If omitted, the filter defaults to All Files (*.*).
												***see AHK "FileSelectFile" command for more info on RootDir and Filter***
												Example: File(C:\Windows|Audio:*.wav; *.mp2; *.mp3) ***note: this is a string not a function or expression, for easy parsing***
			Folder(RootDir) - Adds a button to the right of the input field that when pressed, displays a standard dialog that allows the user to select a folder. Path is displayed in the input field.
											Specify the word "Folder" followed immediately by a string enclosed in parentheses(open and close).
											String format should be: '(RootDir)' (note parentheses) , where RootDir is the root(starting) directory. If 'RootDir' is omitted (e.g. by just specifying 'Folder()') it defaults to the user's "My Documents" folder
											The button is a 25x25 px sized button with a "search folder" icon. A tooltip with the text "Browse folder" is displayed when the mouse hovers over the button.
											Example: Folder(C:\Windows) ***note: this is a string not a function or expression, for easy parsing***

	USAGE: Use the "new" keyword to create a class instance
		Syntax:
			ibxObj := new InputBoxEx(prompt, title, optionsA, action, optionsB*)
		Example:
			ibxObj := new InputBoxEx("Name:|Address:"
															, "Contact Details"
															, "w400 cWhite Font(Tahoma|s9 cMaroon)"
															, "OK|Cancel|Close|Timeout"
															, "Tip(Enter name) Font(Segoe UI|s9)"
															, "R3 Cue(Address here) Font(Segoe UI|s9)")
	RETURN VALUE: A derived object
	*/

	__New(prompt, title:="", optionsA:="", action:="", optionsB*) {
		static i := 0
		__IBExMsg("new", this)
		this.Prompt := [] , this.Title := (title ? title : A_ScriptName) , this.Action := [] , this.OptionsA := optionsA , this.OptionsB := optionsB
		, this.Button := {OK: [], Cancel: [], File : [], Folder: []} , this.Edit := []
		, this.Output := [] , this.Idx := i+=1
		for x, y in {prompt: prompt, action: action} {
			axn := ["OK", "Cancel", "Close", "Timeout"]
			if IsObject(y) {
				for k, v in y
					this[x, ((x = "action") ? axn[k] : k)] := v
			} else {
				for a, b in this.Split(y)
					this[x, ((x = "action") ? axn[a] : a)] := b
			}
		}
	}

	/*
	METHOD: Show()
	DESCRIPTION: Shows the InputBox
	PARAMATER(s):
		options(optional) - String containing any(or none) of following(space-delimited)
			xn - x-position where n is the amount in pixels
			yn - y-position where n is the amount in pixels
			tn - timeout , where n is the amount in milliseconds
	USAGE: ibxObj.Show("x10 y50 t5000")
	RETURN VALUE: If an event has an associated action, particularly a function, this method returns that function's return value(if any) otherwise none
	*/

	Show(options:="") {
		static rxn := {x: "i)\s*\Kx\d+(?=\s*)", y: "i)\s*\Ky\d+(?=\s*)", t: "i)\s*\Kt(\d+)(?=\s*)"}
		; MPV - setting default values because of #warn
		t := 0
		x := 0
		y := 0
		elrv := ""
		if options
			for a, b in rxn
				RegExMatch(options, b, %a%)
		pdhw := A_DetectHiddenWindows
		DetectHiddenWindows, On
		if WinExist("ahk_id " this.Hwnd)
			Gui, % this.Hwnd ":Show" , % (x ? (y ? x " " y : x) : (y ? y : "")) , % this.Title
		else {
			this.Create()
			, this.Show(options)
			, this.Message(0x112, true, 0x0111, true) ; WM_COMMAND:=0x0111 | WM_SYSCOMMAND:=0x112
			/*
			if t {
				;~ start := A_TickCount
				;~ while ((A_TickCount - start) <> t1) ;~ Sleep, % t1
					;~ continue
				;~ this.EventHandler(3)
				WinWaitClose, % "ahk_id " this.Hwnd,, % t1/1000
				if ErrorLevel
					this.EventHandler(3)
			}
			*/
			WinWaitClose, % "ahk_id " this.Hwnd,, % (t ? t1/1000 : "")
			if ErrorLevel ; in case timeout is specified
				this.EventHandler(3)
			Errorlevel := (elrv := this.EventHandler(6)).event ; set ErrorLevel to the event type
		}
		DetectHiddenWindows, % pdhw
		if elrv
			return elrv.retval
	}

	;=================
	;~ PRIVATE METHODS
	;=================

	Create() {
		; REGEX NEEDLES (for maintainability purposes, easier to add/remove/alter)
		static rxn := {color: "\s*(C|c)\K[^\s]*" ; Color
			, font: "i)\s*\K(f|font)\(([^\)]*)\)" ; Font
			, cancel: "i)\s*\K\-Cancel(?=\s*)" ; -Cancel
			, owner: "i)\s*\Kowner[^\s]*" ; Gui Owner
			, mws: "\s+" ; Multiple whitespace(s)
			, width: "i)\s*\Kw(\d+)(?=\s*)" ; Width
			, default: "i)\s*\Kdefault\(([^\)]*)\)" ; Default
			, cue: "i)\s*\Kcue\(([^\)]*)\)" ; Cue Banner
			, tip: "i)\s*\Ktip\(([^\)]*)\)" ; Tooltip
			, file: "i)\s*\Kfile\(([^\)]*)\)" ; File search
			, folder: "i)\s*\Kfolder\(([^\)]*)\)"
			, mid: "\(\K[^\)]*"}
		;~ BUILD GUI
		Gui, New ; Create a nameless GUI to avoid conflict with existing GUI numbers | will identify it via Hwnd
		Gui , % "+LastFound" (RegExMatch(this.optionsA, rxn.owner, owner) ? " +" owner : "") ; GUI options
		this.Hwnd := WinExist() ; Store Hwnd
		optionsA := RegExMatch(this.OptionsA, rxn.font, fontA) ? RegExReplace(this.OptionsA, rxn.font, "") : this.OptionsA
		optionsA := RegExMatch(optionsA, rxn.cancel, cancel) ? RegExReplace(optionsA, rxn.cancel, "") : optionsA
		optionsA := Trim(RegExReplace(optionsA, rxn.mws, A_Space))
		;~ SET COLOR
		if RegExMatch(optionsA, rxn.color, clr)
			Gui, Color , % clr
		;~ SET GUI FONT
		if fontA {
			fA := this.Split(fontA2)
			Gui , Font , % Trim(fA.2), % Trim(fA.1)
		}
		Gui, Margin, 10, 10
		;~ GET BORDER SIZE
		bdr := this.BorderSize(this.Hwnd)
		;~ if !bdr
			;~ bdr := this.BorderSize(this.Hwnd)
		; MPV - setting default values because of #warn
		fontB := ""
		cue := ""
		default := ""
		eO := ""
		file := ""
		folder := ""
		o := ""
		r := ""
		tip := ""
		;~ SET INPUT FIELD(s)
		for x, y in this.Prompt {
			pRow := this.Split(y, "`n").MaxIndex()
			;~ SET PROMPT WIDTH - to automatically wrap long text
			wSub := 20+abs(bdr.L)+abs(bdr.R)
			pW := RegExMatch(optionsA, rxn.width, ibxW) ? (ibxW1-wSub) : (375-wSub)
			Gui , Add, Text , % "w" pW (x == 1 ? " Section" : " xs"), % y ; "r" pRow
			if this.OptionsB[x] {
				eO := this.OptionsB[x]
				for a, b in {fontB: rxn.font, default: rxn.default, cue: rxn.cue, tip: rxn.tip, file: rxn.file, folder: rxn.folder}
					eO := RegExMatch(eO, b, %a%) ? RegExReplace(eO, b, "") : eO
			}
			eO := Trim(RegExReplace(eO, rxn.mws, A_Space))
			Loop, Parse, eO, % A_Space
				if A_LoopField in % (RegExMatch(eO, "i)r(\d+)", r) ? r "," : "") "Password,Number,Uppercase,Lowercase,Upper,Lower,-Wrap,HScroll"
					o .= (A_LoopField = "upper" || A_LoopField = "lower") ? (A_LoopField "case ") : (A_LoopField  " ")
			eO := Trim(o) , o := ""
			if !r
				eO .= " r1" , r1 := 1
			;~ SET INPUT FIELD WIDTH
			wSub := file ? 29+wSub : wSub
			wSub := folder ? (file ? (r1 <= 2 ? 27+wSub : wSub) : 29+wSub) : wSub
			eW := "w" (RegExMatch(optionsA, rxn.width, w) ? (w1 -= wSub) " " : (w1 := 375-wSub) " ") ; 20+n1+n2:=Border Size+Margin | 375:=Default width, same with AHK std InputBox
			Gui, Add, Edit, % eW " " eO " xm y+5", % default ? default1 : ""
			this.Edit[x] := (hEdit := this.GetCtrl()) ; Store Edit control(s) Hwnd
			;~ SET INPUT FIELD FONT
			if fontB {
				fB := this.Split(fontB2)
				Gui , Font , % Trim(fB.2), % Trim(fB.1)
				GuiControl, Font, % this.Edit[x]
				Gui , Font ; Restore to default
				if fontA ; if GUI font is specified, restore back
					Gui , Font , % Trim(fA.2), % Trim(fA.1)
			}
			;~ SET CUE BANNER
			if (cue && r1 == 1)
				this.Cue(cue1)
			;~ SET TOOLTIP
			if tip
				this.ToolTip(this.Edit[x], tip1)
			;~ SET ADDITIONAL "BROWSE FILE/FOLDER" BUTTON(s) - if specified
			if file { ; Browse file button
				Gui , Add , Button , % "x+4 " (r1 <= 1 ? "yp-1 " : "yp ") "w25 h25"
				this.ILButton(this.Button.File[x, "Hwnd"] := this.GetCtrl(), "Shell32.dll:55", 16, 16, 4) ; need to check icon(s) in other systems (Win7 tested)
				this.ToolTip(this.Button.File[x].Hwnd, "Browse file")
				__IBExMsg("btn", this.Button.File[x].Hwnd , this.Idx , x)
				;~ Set options (RootDir, filter)
				this.Button.File[x, "Path"] := this.Split(file1).1 ? this.Split(file1).1 : A_MyDocuments
				filter := this.Split(file1).2 ? RegExReplace(this.Split(file1).2, "\:", A_Space "(") : ""
				filter .= filter ? ")" : ""
				this.Button.File[x, "Filter"] := filter
			}
			if folder { ; Browse folder button
				Gui , Add , Button , % (file ? (r1 <= 2 ? "x+2 yp " : "xp y+1 ") : "x+4 " (r1 <= 1 ? "yp-1 " : "yp ")) "w25 h25"
				this.ILButton(this.Button.Folder[x, "Hwnd"] := this.GetCtrl(), "imageres.dll:204", 16, 16, 4)
				this.ToolTip(this.Button.Folder[x].Hwnd , "Browse folder")
				__IBExMsg("btn", this.Button.Folder[x].Hwnd , this.Idx , x)
				this.Button.Folder[x, "Path"] := folder1 ? folder1 : A_MyDocuments
			}
		}
		;~ SET OK/CANCEL BUTTON(s)
		if cancel { ; Single-button(OK only)
			Gui, Add, Button , % "y+20 w" (w := 90) " x" (((w1+wSub)/2)-(w/2)) " h25", OK
			this.Button.OK["Hwnd"] := this.GetCtrl(), this.Button.Cancel["Hwnd"] := false ; Store Hwnd
			this.ILButton(this.Button.OK.Hwnd, "comres.dll:8", 16, 16, 0) ; Set button image
			__IBExMsg("btn", this.Button.OK.Hwnd , this.Idx , 0)
		} else { ; Default: 2 buttons(OK & Cancel)
			Gui, Add, Button, % "y+20 w" (w := 90) " x" (((w1+wSub)/2)-(((w*2) +10)/2)) " h25" , OK
			hBtn1 := this.GetCtrl()
			Gui, Add, Button, x+10 yp wp hp, Cancel
			hBtn2 :=  this.GetCtrl()
			for a, b in [8, 10] {
				this.Button[a == 1 ? "OK" : "Cancel", "Hwnd"] := hBtn%a%
				this.ILButton(hBtn%a%, "comres.dll:" b, 16, 16, 0, "2,2,0,2")
				__IBExMsg("btn", hBtn%a% , this.Idx , 0)
			}
		}
	}

	Destroy() {
		Gui, % this.Hwnd ":Destroy"
		this.Message(0x112, false, 0x0111, false) ; WM_COMMAND:=0x0111 | WM_SYSCOMMAND:=0x112
		__IBExMsg("del", this.Idx)
	}

	EventHandler(event:=1, param:="") {
		; MPV - setting default values because of #warn
		static e := {0: "OK", 1: "Cancel", 2: "Close", 3: "Timeout"} , rv := "" , ev := ""
		if (event <= 3) {
			for a, b in this.Edit {
				GuiControlGet, f, % this.Hwnd ":", % b
				this.Output[a] := f
			}
			ev := e[event]
			ErrorLevel := ev
			if (axn := this.Action[ev]) {
				Gui , % this.Hwnd ":Cancel"
				if (IsLabel(axn) && !IsFunc(axn))
					gosub % axn
				else if (IsFunc(axn) && !IsLabel(axn)) {
					if ((fn := Func(axn)).MaxParams >= 2 && fn.MinParams <= 2)
						rv := fn.([this.Output]*)
				} else if (IsLabel(axn) && IsFunc(axn))
					gosub % axn
			}
			this.Destroy()
		}
		if (event == 4) { ; Select file button
			Gui , % this.Hwnd ":+OwnDialogs"
			FileSelectFile, file ,, % this.Button.File[param].Path, % "Select File - " this.Title, % this.Button.File[param].Filter
			if !ErrorLevel
				GuiControl, % this.Hwnd ":", % this.Edit[param] , % file
		}
		if (event == 5) { ; Select folder button
			Gui , % this.Hwnd ":+OwnDialogs"
			FileSelectFolder, folder , % this.Button.Folder[param].Path,, % "Select Folder - " this.Title
			if !ErrorLevel
				GuiControl, % this.Hwnd ":", % this.Edit[param] , % folder
		}
		if (event == 6) {
			elrv := []
			elrv["retval"] := rv , elrv["event"] := ev
			rv := "" , ev := ""
			return elrv
		}
	}

	Message(params*) {
		static fn := [] , fnc := "__IBExMsg"
		for a, b in params {
			if Mod(a, 2) {
				f := OnMessage(b, (m := params[a+1]) ? fnc : (fn.HasKey(b) ? fn[b] : ""))
				if (f && f <> fnc && m)
					fn[b] := f
			}
		}
	}

	Split(str, delim:="|") {
		split := []
		Loop, Parse, str, % delim, % A_Space A_Tab
			split[A_Index] := A_LoopField
		return split
	}

	GetCtrl(idx:=0, option:="Hwnd") { ; idx:=0("last control added") option:=[Hwnd, ClassNN]
		cmd := {Hwnd: "ControlListHwnd", ClassNN: "ControlList"}
		pdhw := A_DetectHiddenWindows
		DetectHiddenWindows, On
		WinGet, Ctrls, % cmd[option], % "ahk_id " this.Hwnd
		DetectHiddenWindows, % pdhw
		c := this.Split(Ctrls, "`n")
		return c[!idx ? c.MaxIndex() : idx]
		;~ if RegExMatch(Ctrls, "[^\n]*$", ctrl)
			;~ return ctrl
	}

	Cue(lpMultiByteStr, idx:=0) {
		if A_IsUnicode
			WideCharStr := lpMultiByteStr
		else {
			nSize := DllCall("MultiByteToWideChar", "UInt", (CP_ACP := 0), "Uint", 0, (PtrType := (A_PtrSize=8) ? "Ptr" : "UInt", lpMultiByteStr, "Int"), -1, "UInt", 0, "Int", 0)
			VarSetCapacity(WideCharStr, nSize*2, 0)
			DllCall("MultiByteToWideChar", "UInt", CP_ACP, "UInt", 0, PtrType, lpMultiByteStr, "Int", nSize, "Str", WideCharStr, "Int", nSize)
		}
		SendMessage, 0x1501, true, &WideCharStr,, % "ahk_id " this.Edit[idx ? idx : this.Edit.MaxIndex()] ; EM_SETCUEBANNER
		return ErrorLevel
	}

	ToolTip(hCtrl:="", text:="", modify:=0) {
		; MPV - setting default values because of #warn
		static hTT :=0 , hGui := 0, Ptr := 0
		hParent := DllCall("GetParent", "Ptr", hCtrl)
		if (hParent <> hGui) {
		;~ If (!hTT) {
			;~ hGui := DllCall("GetParent", "Ptr", hCtrl)
			hGui := hParent
			hTT := DllCall("CreateWindowEx", "Uint", 0, "Str", "TOOLTIPS_CLASS32", "Uint", 0, "Uint", 2147483648 | 3, "Uint", -2147483648
				, "Uint", -2147483648, "Uint", -2147483648, "Uint", -2147483648, "Uint", hGui, "Uint", 0, "Uint", 0, "Uint", 0)
			Ptr := (A_PtrSize ? "Ptr" : "UInt") ; , DllCall("uxtheme\SetWindowTheme","Uint",hTT,Ptr,0,"UintP",0)	; TTM_SETWINDOWTHEME
		}
		Varsetcapacity(TInfo, 44, 0), Numput(44, TInfo), Numput(1|16, TInfo, 4), Numput(hGui, TInfo, 8), Numput(hCtrl, TInfo, 12), Numput(&text, TInfo, 36)
		!modify ? (DllCall("SendMessage", Ptr, hTT, "Uint", 1028, Ptr, 0, Ptr, &TInfo, Ptr)) ; TTM_ADDTOOL := 1028 (add a tool and assign to control)
			. (DllCall("SendMessage", Ptr, hTT, "Uint", 1048, Ptr, 0, Ptr, A_ScreenWidth)) ; TTM_SETMAXTIPWIDTH := 1048 (Multi-line tooltip)
		DllCall("SendMessage", Ptr, hTT, "UInt", (A_IsUnicode ? 0x439 : 0x40c), Ptr, 0, Ptr, &TInfo, Ptr) ; TTM_UPDATETIPTEXT (OLD_MSG:=1036) (Modify Tooltip text)
	}

	BorderSize(hwnd) {
		; MPV - setting default values because of #warn
	    pt := 0
	    ClientX := 0
	    ClientY := 0
		WinGetPos, WinX, WinY, WinW, WinH, ahk_id %hwnd%
		VarSetCapacity(rc, 16) , DllCall("GetClientRect", "uint", hwnd, "uint", &rc) , ClientW := NumGet(rc, 8, "int") , ClientH := NumGet(rc, 12, "int")
		NumPut(ClientX, pt, 0) , NumPut(ClientY, pt, 4) , VarSetCapacity(pt, 16) , DllCall("ClientToScreen", "uint", hwnd, "uint", &pt)
		ClientX := NumGet(pt, 0, "int") , ClientY := NumGet(pt, 4, "int") , Top := ClientY - WinY , Left := ClientX - WinX
		Bottom := WinH - ClientH - Top , Right := WinW - ClientW - Left
		return {L: Left, R: Right, T: Top, B: Bottom}
	}

	/*
	Title: ILButton
	Version: 1.1
	Author: tkoi <http://www.autohotkey.net/~tkoi>
	License: GNU GPLv3 <http://www.opensource.org/licenses/gpl-3.0.html>

	Function: ILButton()
		Creates an imagelist and associates it with a button.
	Parameters:
		hBtn   - handle to a buttton
		images - a pipe delimited list of images in form "file:zeroBasedIndex"
				   - file must be of type exe, dll, ico, cur, ani, or bmp
				   - there are six states: normal, hot (hover), pressed, disabled, defaulted (focused), and stylushot
					   - ex. "normal.ico:0|hot.ico:0|pressed.ico:0|disabled.ico:0|defaulted.ico:0|stylushot.ico:0"
				   - if only one image is specified, it will be used for all the button's states
				   - if fewer than six images are specified, nothing is drawn for the states without images
				   - omit "file" to use the last file specified
					   - ex. "states.dll:0|:1|:2|:3|:4|:5"
				   - omitting an index is the same as specifying 0
				   - note: within vista's aero theme, a defaulted (focused) button fades between images 5 and 6
		cx     - width of the image in pixels
		cy     - height of the image in pixels
		align  - an integer between 0 and 4, inclusive. 0: left, 1: right, 2: top, 3: bottom, 4: center
		margin - a comma-delimited list of four integers in form "left,top,right,bottom"

	Notes:
		A 24-byte static variable is created for each IL button
		Tested on Vista Ultimate 32-bit SP1 and XP Pro 32-bit SP2.

	Changes:
	  v1.1
		Updated the function to use the assume-static feature introduced in AHK version 1.0.48
	*/

	ILButton(hBtn, images, cx=16, cy=16, align=4, margin="1,1,1,1") { ; http://www.autohotkey.com/board/topic/37147-ilbutton-image-buttons-with-text-states-alignment/
		static
		static i = 0
		; MPV - setting default values because of #warn
		local himl, v0, v1, v2, v3, ext, hbmp, hicon := 0
		i ++

		himl := DllCall("ImageList_Create", "UInt", cx, "UInt", cy, "UInt", 0x20, "UInt", 1, "UInt", 5)
		Loop, Parse, images, |
		{
			StringSplit, v, A_LoopField, :
			if not v1
				v1 := v3
			v3 := v1
			SplitPath, v1, , , ext
			if (ext = "bmp") {
				hbmp := DllCall("LoadImage", "UInt", 0, "Str", v1, "UInt", 0, "UInt", cx, "UInt", cy, "UInt", 0x10)
				, DllCall("ImageList_Add", "UInt", himl, "UInt", hbmp, "UInt", 0)
				, DllCall("DeleteObject", "UInt", hbmp)
			} else {
				DllCall("PrivateExtractIcons", "Str", v1, "UInt", v2, "UInt", cx, "UInt", cy, "UIntP", hicon, "UInt", 0, "UInt", 1, "UInt", 0)
				, DllCall("ImageList_AddIcon", "UInt", himl, "UInt", hicon)
				, DllCall("DestroyIcon", "UInt", hicon)
			}
		}
		; Create a BUTTON_IMAGELIST structure
		VarSetCapacity(struct%i%, 24)
		NumPut(himl, struct%i%, 0, "UInt")
		Loop, Parse, margin, `,
			NumPut(A_LoopField, struct%i%, A_Index * 4, "UInt")
		NumPut(align, struct%i%, 20, "UInt")
		; BCM_FIRST := 0x1600, BCM_SETIMAGELIST := BCM_FIRST + 0x2
		PostMessage, 0x1602, 0, &struct%i%, , ahk_id %hBtn%
		Sleep 1 ; workaround for a redrawing problem on WinXP
	}

}

__IBExMsg(wParam, lParam, msg:="", hwnd:="") {
	static ix := [] , i := 0 , btn := [] , WM_COMMAND := 0x0111 , WM_SYSCOMMAND := 0x112 , BN_CLICKED := 0x0000 , SC_CLOSE := 0xF060
	if (wParam == "new" && IsObject(lParam))
		ix[i += 1] := lParam
	if (wParam == "del") { ; Remove stored info in object upon calling Destroy() method
		keys := []
		for x, y in btn
			if (y.o == lParam)
				keys.Insert(x)
		for a, b in keys
			if btn.HasKey(b)
				btn.Remove(b, "")
	}
	if (wParam == "btn")
		btn[lParam] := {o: msg , b: hwnd}
	if (msg == WM_COMMAND) {
		lo := wParam & 0xFFFF
		hi := wParam >> 16
		if (hi == BN_CLICKED) {
			if btn.HasKey(lParam) {
				j := btn[lParam].o , k := btn[lParam].b
				if (lParam == ix[j].Button.OK.Hwnd) ; Button OK
					ix[j].EventHandler(0)
				if (lParam == ix[j].Button.Cancel.Hwnd) ; Button Cancel
					ix[j].EventHandler(1)
				if (lParam == ix[j].Button.File[k].Hwnd) ; Browse File
					ix[j].EventHandler(4, k)
				if (lParam == ix[j].Button.Folder[k].Hwnd) ; Browse Folder
					ix[j].EventHandler(5, k)
			}
		}
	}
	if (msg == WM_SYSCOMMAND && wParam == SC_CLOSE) {
		Loop, % ix.MaxIndex()
			if (hwnd == ix[A_Index].Hwnd)
				ix[A_Index].EventHandler(2)
	}
}



MikeV
  • Members
  • 19 posts
  • Last active: Sep 13 2015 09:08 PM
  • Joined: 03 Jun 2013

So, for some reason (unknown to me), the above scripts will NOT show any icons.  After searching around, I found an updated version of ILButton() which does show the icons.  I found it here:  http://www.autohotke...lignment/page-3

 

Here is the currently working script:

#SingleInstance force
#Warn

#f10::
/*
            Tip(TipText) - Sets/creates the tooltip that is displayed every time the mouse hovers over the input field/edit control.
                                    Specify the word "Tip" followed immediately by a string enclosed in parentheses(open and close)
                                    Example: Tip(This is a tooltip) ***note: this is a string not a function or expression, for easy parsing***
*/

    ; can use either the pipe-delimited string or the array
    inputItems := "Normal Input:|Multi-line Input:|lower only:|UPPER ONLY:|Numbers Only:|Password:|File Only:|Folder Only:"
    inputArray := ["Normal Input:", "Multi-line Input:", "lower only:", "UPPER ONLY:", "Numbers Only:", "Password:", "File Only:", "Folder Only:"]
    title := "Multi-input Demo"
    opts := "w600 Font(Segoe UI|s9 c0F0F80)"
    inputFont := " Font(Calibri|s10 cBlack Normal)"
    inputNormal := "R1 Default(Just a simple sentence.) Tip(111)" . inputFont
    inputMultiline := "R3 -Wrap HScroll Tip(This control accepts multiple lines of text)" . inputFont
    inputLower := "R1 Lower Tip(333)" . inputFont
    inputUpper := "R1 Upper Tip(444)" . inputFont
    inputNumber := "R1 Number Tip(555)" . inputFont
    inputPassword := "R1 Password Tip(666)" . inputFont
    inputFile := "R1 Cue(Select a text file...) File(|Text File:*.txt) Tip(777)" . inputFont
    inputFolder := "R1 Cue(Select a folder...) Folder(Computer) Tip(888)" . inputFont

    ibx := new InputBoxEx(inputItems, title, opts, "", inputNormal, inputMultiline, inputLower, inputUpper, inputNumber, inputPassword, inputFile, inputFolder)
    ibx.show()
    if (ErrorLevel == "OK") {
        askOK(ibx.Output)
    }
    else if (ErrorLevel == "Cancel") {
        askCancel()
    }
    else if (ErrorLevel == "Close") {
        askClose()
    }
    else if (ErrorLevel == "Timeout") {
        askTimeout()
    }
    return

askOK(values) {
    message(toString(values))
}

askCancel() {
    ;message("CANCEL was clicked.")
}

askClose() {
    ;message("CLOSE was clicked.")
}

askTimeout() {
    ;message("Timeout occurred.")
}

message(msg) {
    MsgBox % msg
}

toString(array, depth:=6, indent:="") {
    static EOL := "`n"
    result := ""
    if (IsObject(array)) {
        for key, value in array {
            result .= "`t" . indent . key
            if (IsObject(value) && depth > 1) {
                result .= EOL . toString(value, depth - 1, indent . "`t")
            }
            else {
                result .= "`t= [" . value . "]" . EOL
            }
        }
    }
    else {
        result := array
    }
    return result
}

/*
CLASS: InputBoxEx
DESCRIPTION: Provides users an easy way to create customized/dynamic InputBoxes.
REQUIREMENT(s): AutoHotkey v. 1.1.9.x (I'm using the ":=" operator for optional parameters in functions/methods)
FEATURES: All options available in the standard AHK InputBox+more(see below)
    - Option to create multiple input fields
    - GUI can be customized [color and font]
    - Each input field can be customized
        * prompt
        * font[name,color,size,style]
        * no. of rows[single or multiline]
        * input type[numbers only]
        * display type[password, uppercase, lowercase]
        * attach tooltip
        * set cue banner
        * add button(s) that allow users to select a file or a folder
    - Option to remove or show the "Cancel" button
    - Option to associate a label or a function with a particular event.
        Event types:
            * OK - User clicked the OK button
            * Cancel - User clicked the Cancel button
            * Close - User dismissed the InputBox via the Close button
            * Timeout - InputBox timed out
    - More features are to be added
USAGE: Script designer need only call two methods
    - Use #Include InputBoxEx.ahk or #Include <InputBoxEx> (if script is located in your lib folder) or copy code and paste it on your script
    - Instantiate the class using the "new" keyword and pass the appropriate parameter(s)
        e.g. ibxObj := new InputBoxEx(params*) -for parameters, see documentation for __New() method-
    - Then show the InputBox using the Show() method
        e.g. ibxObj.Show(param) -for parameters, see documentation for Show() method-
NOTES:
    Behavior:
        - It behaves pretty much the same as the standard AHK InputBox.
        - When the user calls the Show() method, the lines below it will not be executed until the InputBox is dismissed (via OK, Cancel, Close or Timeout). ***similar to MsgBox and AHK InputBox
        - If an associated action(label or function) is assigned to an event, that subroutine will be executed first then script will continue at the lines(if any) after the Show() method.
        - Text(output) that the user has input can be retieved via the ibxObj.Output[x](which is an object) property, where x is the index of a particualr input field.
            e.g. Field1 := ibxObj.Output.1 (or ibxObj.Output[1]) and so on...
        - When the user dismisses the InputBox, ErrorLevel is set to either of the following values(depending on the event): "OK", "Cancel", "Close", "Timeout"
            *** if an event has an associated action(laber or function), and if within that subroutine ErrorLevel's value is altered by a command(e.g. Run), upon return from that subroutine,
            ErrorLevel is reset back to the "event" type. This allows users to retrieve the event type via ErrorLevel on the lines after the Show() method call.
        - When associating a function with an event, the function should have atleast 1 parameter & the minimum required parameter(s) should be equal to or less than 1.
            *** The class passes an array/object to the function's 1st parameter which contains whatever the user has input.
            *** The associated function's return value (if any) is returned by the Show() method. e.g.: IBX_EventRetVal := ibxObj.Show(param)
    Others/ToDo's/Issues:
        - [issue] Currently, InputBoxEx is not manually resizable (will work on this)
        - [issue] Button images might appear different on other systems. Tested only on Win7, Dll files have different icon nos on different systems (will fix this)
        - [intentional] InputBoxEx height is not configurable, this is done automatically.
        - [intentional] Unlike input field(s) whose font can be set individually, "prompt" font is uniform and follows the whatever is set by the user for the GUI font (might allow customization)
        - [todo] Implement option to allow users to add a ComboBox and/or a DateTime control instead of just an Edit control as an input field.
        - [todo] Option to add an UpDown control and use the input field as a buddy control.
        - [todo?] Might add option to allow users to add Checkboxes and Radio controls
        - [todo?] Option to add custom buttons with custom actions
CREDITS:
    Credits to tkoi and corrupt for the ILButton() function. (http://www.autohotkey.com/board/topic/37147-ilbutton-image-buttons-with-text-states-alignment/)
    Credits to sbc for the GetBorderSize() function. (http://www.autohotkey.com/board/topic/60410-function-getbordersize/)
    Credits to art for AddToolTip() function, I altered it a bit though. (http://www.autohotkey.com/board/topic/27670-add-tooltips-to-controls/page-2#entry431059)
*/

class InputBoxEx
{

    /*
    METHOD: __New()
    DESCRIPTION: Constructor for the class
    PARAMETER(s):
        prompt(required) - Text to display, a message to the user indicating what kind of input is expected
                                            User can pass a string or an object/array. If string, it can be a pipe-delimited list of text items to display, no. of items indicates the no. of input field(s)
                                            Example: (String) - "Prompt One|Prompt Two|Prompt Three" ; (Object/Array) - ["Prompt One", "Prompt Two" , "Prompt Three"]

        title(optional) - Title of the InputBox, If blank or omitted, it defaults to the name of the script.

        optionsA(optional) - InputBox GUI options. String containing any(or none) of following(space-delimited). If blank, default settings will be used(see description below)
                                            Example: "w450 c0xFFFFFF Font(Segoe UI|s9 cRed)"
            Wn - Width of the InputBox, where n is the amount in pixels. If omitted, it defaults to 375(same with AHK standard InputBox)
            C - GUI color. Specify the letter "C" followed immediately by a color name(see color chart in AHK help file) or RGB value (the 0x prefix is optional).
            Font(FontName|FontOptions) or F(FontName|FontOptions) - GUI font. Specify the word "Font" or the letter "F" (not case-sensitive) followed by immediately by a string enclosed in parentheses(open and close)
                                                                                                                        String format should be: '(Font Name|Font Options)' (note pipe delimiter and parentheses), where "Font Name" is the name of the font
                                                                                                                        and "Font Options" is a space-delimited string containing standard font options(see AHK "Gui, Font" command for a list of options. e.g.: "cRed Italic s9")
                                                                                                                        Example: Font(Segoe UI|cRed s10 Italic Underline) ***note: this is a string not a function or expression, for easy parsing***
            -Cancel - Specify the word "-Cancel" to remove the "Cancel" button. InputBox will only have the "OK" button if this option is specified. Default is two buttons: OK and Cancel

        action(optional) - Label or Funcion to call for each InputBoxEx event. There are 4 events: OK, Cancel, Close, Timeout. (Self-explanatory)
                                        User can pass a string or an object/array. If string, it should be a pipe-delimited list(max=4 items) of labels or functions names where item1 corresponds to the "OK" event
                                        , item2 to the "Cancel" event, item3 to the "Close" event and item4 to the "Timeout" event. If no label or function is specified for a particular event , no action is taken.
                                        When InputBoxEx is dismissed via any of the events stated above, user can retrieve the event type/name via the obj.Event property. (This is similar to ErrorLevel for the standard AHK InputBox)
                                        Example: "LabelOK|LabelCancel|LabelClose|LabelTimeout" or if Array/object: ["LabelOK", "LabelCancel", "LabelClose", "LabelTimeout"] ***see notes reagrding InputBoxEx behavior***

        optionsB(variadic) - InputBox input field option(s). String containing any(or none) of following(space-delimited). If blank, default settings will be used(see description below).
                                            Number of parameters must be equal or less than the number of items in the "prompt" parameter. Each parameter corresponds to each prompt/field.
                                            Example: "R5 Default(This is the default text) Folder(C:\Windows\System32) Font(Consolas|s9 cBlue Italic) File(C:\Users\Username\Pictures|Image Files:*.jpg; *.png; *.bmp)"
            R - Number of rows of the input field(Edit control). Specify the letter "R" followed immediately by a number. If omitted it defaults to 1. (e.g.: R5 - five rows)
            Password - Hides the user's input. Similar to "Hide" option for the standard AHK InputBox. This option has no effect for multi-line input fields
            Number - Prevents the user from typing anything other than digits into the field.
            Uppercase or Upper - The characters typed by the user are automatically converted to uppercase.
            Lowercase or Lower - The characters typed by the user are automatically converted to lowercase.
            -Wrap - Turns off word-wrapping in a multi-line input field.
            HScroll - Adds a horizontal scrollbar, useful for input fields with the "-Wrap" option.
            Font(FontName|FontOptions) or F(FontName|FontOptions) - Input field font. For usage/format, see same option for "optionsA" parameter above.
            Default(DefaultText) - A string that will appear in the InputBox's edit field when the dialog first appears.
                                                    Specify the word "Default" followed immediately by a string enclosed in parentheses(open and close)
                                                    Example: Default(This is the default text.) ***note: this is a string not a function or expression, for easy parsing***
            Cue(CueText) - Sets the textual cue, or tip, that is displayed by the input field to prompt the user for information.
                                        Specify the word "Cue" followed immediately by a string enclosed in parentheses(open and close)
                                        Example: Cue(This is a cue banner) ***note: this is a string not a function or expression, for easy parsing***
                                        This option has no effect for multi-line edit controls.
            Tip(TipText) - Sets/creates the tooltip that is displayed every time the mouse hovers over the input field/edit control.
                                    Specify the word "Tip" followed immediately by a string enclosed in parentheses(open and close)
                                    Example: Tip(This is a tooltip) ***note: this is a string not a function or expression, for easy parsing***
            File(RootDir|Filter) - Adds a button to the right of the input field that when pressed, displays a standard dialog that allows the user to select a file.
                                                Specify the word "File" followed immediately by a string enclosed in parentheses(open and close).
                                                String format should be: '(RootDir|Filter)' (note pipe delimiter and parentheses), where RootDir is the root(starting) directory and filter indicates which types of files are shown by the dialog.
                                                If 'RootDir' is omitted (e.g. by just specifying 'File(|Filter)', **note a pipe "|" is still specified or else 'Filter' will be assumed as 'RootDir'**) it defaults to the user's "My Documents" folder
                                                The button is a 25x25 px sized button with a "search folder" icon. A tooltip with the text "Browse file" is displayed when the mouse hovers over the button.
                                                ***FILTER SYNTAX: [FileType:*FileExt1; *FileExt2] (note colon[:] delimiter between "FileType" and "FileExt" and also the semi-colon[;]+space that separates each file extension). If omitted, the filter defaults to All Files (*.*).
                                                ***see AHK "FileSelectFile" command for more info on RootDir and Filter***
                                                Example: File(C:\Windows|Audio:*.wav; *.mp2; *.mp3) ***note: this is a string not a function or expression, for easy parsing***
            Folder(RootDir) - Adds a button to the right of the input field that when pressed, displays a standard dialog that allows the user to select a folder. Path is displayed in the input field.
                                            Specify the word "Folder" followed immediately by a string enclosed in parentheses(open and close).
                                            String format should be: '(RootDir)' (note parentheses) , where RootDir is the root(starting) directory. If 'RootDir' is omitted (e.g. by just specifying 'Folder()') it defaults to the user's "My Documents" folder
                                            The button is a 25x25 px sized button with a "search folder" icon. A tooltip with the text "Browse folder" is displayed when the mouse hovers over the button.
                                            Example: Folder(C:\Windows) ***note: this is a string not a function or expression, for easy parsing***

    USAGE: Use the "new" keyword to create a class instance
        Syntax:
            ibxObj := new InputBoxEx(prompt, title, optionsA, action, optionsB*)
        Example:
            ibxObj := new InputBoxEx("Name:|Address:"
                                                            , "Contact Details"
                                                            , "w400 cWhite Font(Tahoma|s9 cMaroon)"
                                                            , "OK|Cancel|Close|Timeout"
                                                            , "Tip(Enter name) Font(Segoe UI|s9)"
                                                            , "R3 Cue(Address here) Font(Segoe UI|s9)")
    RETURN VALUE: A derived object
    */

    __New(prompt, title:="", optionsA:="", action:="", optionsB*) {
        static i := 0
        __IBExMsg("new", this)
        this.Prompt := [] , this.Title := (title ? title : A_ScriptName) , this.Action := [] , this.OptionsA := optionsA , this.OptionsB := optionsB
        , this.Button := {OK: [], Cancel: [], File : [], Folder: []} , this.Edit := []
        , this.Output := [] , this.Idx := i+=1
        for x, y in {prompt: prompt, action: action} {
            axn := ["OK", "Cancel", "Close", "Timeout"]
            if IsObject(y) {
                for k, v in y
                    this[x, ((x = "action") ? axn[k] : k)] := v
            } else {
                for a, b in this.Split(y)
                    this[x, ((x = "action") ? axn[a] : a)] := b
            }
        }
    }

    /*
    METHOD: Show()
    DESCRIPTION: Shows the InputBox
    PARAMATER(s):
        options(optional) - String containing any(or none) of following(space-delimited)
            xn - x-position where n is the amount in pixels
            yn - y-position where n is the amount in pixels
            tn - timeout , where n is the amount in milliseconds
    USAGE: ibxObj.Show("x10 y50 t5000")
    RETURN VALUE: If an event has an associated action, particularly a function, this method returns that function's return value(if any) otherwise none
    */

    Show(options:="") {
        static rxn := {x: "i)\s*\Kx\d+(?=\s*)", y: "i)\s*\Ky\d+(?=\s*)", t: "i)\s*\Kt(\d+)(?=\s*)"}
        ; MPV - setting default values because of #warn
        t := 0
        x := 0
        y := 0
        elrv := ""
        if options
            for a, b in rxn
                RegExMatch(options, b, %a%)
        pdhw := A_DetectHiddenWindows
        DetectHiddenWindows, On
        if WinExist("ahk_id " this.Hwnd)
            Gui, % this.Hwnd ":Show" , % (x ? (y ? x " " y : x) : (y ? y : "")) , % this.Title
        else {
            this.Create()
            , this.Show(options)
            , this.Message(0x112, true, 0x0111, true) ; WM_COMMAND:=0x0111 | WM_SYSCOMMAND:=0x112
            /*
            if t {
                ;~ start := A_TickCount
                ;~ while ((A_TickCount - start) <> t1) ;~ Sleep, % t1
                    ;~ continue
                ;~ this.EventHandler(3)
                WinWaitClose, % "ahk_id " this.Hwnd,, % t1/1000
                if ErrorLevel
                    this.EventHandler(3)
            }
            */
            WinWaitClose, % "ahk_id " this.Hwnd,, % (t ? t1/1000 : "")
            if ErrorLevel ; in case timeout is specified
                this.EventHandler(3)
            Errorlevel := (elrv := this.EventHandler(6)).event ; set ErrorLevel to the event type
        }
        DetectHiddenWindows, % pdhw
        if elrv
            return elrv.retval
    }

    ;=================
    ;~ PRIVATE METHODS
    ;=================

    Create() {
        ; REGEX NEEDLES (for maintainability purposes, easier to add/remove/alter)
        static rxn := {color: "\s*(C|c)\K[^\s]*" ; Color
            , font: "i)\s*\K(f|font)\(([^\)]*)\)" ; Font
            , cancel: "i)\s*\K\-Cancel(?=\s*)" ; -Cancel
            , owner: "i)\s*\Kowner[^\s]*" ; Gui Owner
            , mws: "\s+" ; Multiple whitespace(s)
            , width: "i)\s*\Kw(\d+)(?=\s*)" ; Width
            , default: "i)\s*\Kdefault\(([^\)]*)\)" ; Default
            , cue: "i)\s*\Kcue\(([^\)]*)\)" ; Cue Banner
            , tip: "i)\s*\Ktip\(([^\)]*)\)" ; Tooltip
            , file: "i)\s*\Kfile\(([^\)]*)\)" ; File search
            , folder: "i)\s*\Kfolder\(([^\)]*)\)"
            , mid: "\(\K[^\)]*"}
        ;~ BUILD GUI
        Gui, New ; Create a nameless GUI to avoid conflict with existing GUI numbers | will identify it via Hwnd
        Gui , % "+LastFound" (RegExMatch(this.optionsA, rxn.owner, owner) ? " +" owner : "") ; GUI options
        this.Hwnd := WinExist() ; Store Hwnd
        optionsA := RegExMatch(this.OptionsA, rxn.font, fontA) ? RegExReplace(this.OptionsA, rxn.font, "") : this.OptionsA
        optionsA := RegExMatch(optionsA, rxn.cancel, cancel) ? RegExReplace(optionsA, rxn.cancel, "") : optionsA
        optionsA := Trim(RegExReplace(optionsA, rxn.mws, A_Space))
        ;~ SET COLOR
        if RegExMatch(optionsA, rxn.color, clr)
            Gui, Color , % clr
        ;~ SET GUI FONT
        if fontA {
            fA := this.Split(fontA2)
            Gui , Font , % Trim(fA.2), % Trim(fA.1)
        }
        Gui, Margin, 10, 10
        ;~ GET BORDER SIZE
        bdr := this.BorderSize(this.Hwnd)
        ;~ if !bdr
            ;~ bdr := this.BorderSize(this.Hwnd)
        ; MPV - setting default values because of #warn
        fontB := ""
        cue := ""
        default := ""
        eO := ""
        file := ""
        folder := ""
        o := ""
        r := ""
        tip := ""
        ;~ SET INPUT FIELD(s)
        for x, y in this.Prompt {
            pRow := this.Split(y, "`n").MaxIndex()
            ;~ SET PROMPT WIDTH - to automatically wrap long text
            wSub := 20+abs(bdr.L)+abs(bdr.R)
            pW := RegExMatch(optionsA, rxn.width, ibxW) ? (ibxW1-wSub) : (375-wSub)
            Gui , Add, Text , % "w" pW (x == 1 ? " Section" : " xs"), % y ; "r" pRow
            if this.OptionsB[x] {
                eO := this.OptionsB[x]
                for a, b in {fontB: rxn.font, default: rxn.default, cue: rxn.cue, tip: rxn.tip, file: rxn.file, folder: rxn.folder}
                    eO := RegExMatch(eO, b, %a%) ? RegExReplace(eO, b, "") : eO
            }
            eO := Trim(RegExReplace(eO, rxn.mws, A_Space))
            Loop, Parse, eO, % A_Space
                if A_LoopField in % (RegExMatch(eO, "i)r(\d+)", r) ? r "," : "") "Password,Number,Uppercase,Lowercase,Upper,Lower,-Wrap,HScroll"
                    o .= (A_LoopField = "upper" || A_LoopField = "lower") ? (A_LoopField "case ") : (A_LoopField  " ")
            eO := Trim(o) , o := ""
            if !r
                eO .= " r1" , r1 := 1
            ;~ SET INPUT FIELD WIDTH
            wSub := file ? 29+wSub : wSub
            wSub := folder ? (file ? (r1 <= 2 ? 27+wSub : wSub) : 29+wSub) : wSub
            eW := "w" (RegExMatch(optionsA, rxn.width, w) ? (w1 -= wSub) " " : (w1 := 375-wSub) " ") ; 20+n1+n2:=Border Size+Margin | 375:=Default width, same with AHK std InputBox
            Gui, Add, Edit, % eW " " eO " xm y+5", % default ? default1 : ""
            this.Edit[x] := (hEdit := this.GetCtrl()) ; Store Edit control(s) Hwnd
            ;~ SET INPUT FIELD FONT
            if fontB {
                fB := this.Split(fontB2)
                Gui , Font , % Trim(fB.2), % Trim(fB.1)
                GuiControl, Font, % this.Edit[x]
                Gui , Font ; Restore to default
                if fontA ; if GUI font is specified, restore back
                    Gui , Font , % Trim(fA.2), % Trim(fA.1)
            }
            ;~ SET CUE BANNER
            if (cue && r1 == 1)
                this.Cue(cue1)
            ;~ SET TOOLTIP
            if tip
                this.ToolTip(this.Edit[x], tip1)
            ;~ SET ADDITIONAL "BROWSE FILE/FOLDER" BUTTON(s) - if specified
            if file { ; Browse file button
                Gui , Add , Button , % "x+4 " (r1 <= 1 ? "yp-1 " : "yp ") "w25 h25"
                this.ILButton(this.Button.File[x, "Hwnd"] := this.GetCtrl(), "Shell32.dll:55", 16, 16, 4) ; need to check icon(s) in other systems (Win7 tested)
                this.ToolTip(this.Button.File[x].Hwnd, "Browse file")
                __IBExMsg("btn", this.Button.File[x].Hwnd , this.Idx , x)
                ;~ Set options (RootDir, filter)
                this.Button.File[x, "Path"] := this.Split(file1).1 ? this.Split(file1).1 : A_MyDocuments
                filter := this.Split(file1).2 ? RegExReplace(this.Split(file1).2, "\:", A_Space "(") : ""
                filter .= filter ? ")" : ""
                this.Button.File[x, "Filter"] := filter
            }
            if folder { ; Browse folder button
                Gui , Add , Button , % (file ? (r1 <= 2 ? "x+2 yp " : "xp y+1 ") : "x+4 " (r1 <= 1 ? "yp-1 " : "yp ")) "w25 h25"
                this.ILButton(this.Button.Folder[x, "Hwnd"] := this.GetCtrl(), "imageres.dll:204", 16, 16, 4)
                this.ToolTip(this.Button.Folder[x].Hwnd , "Browse folder")
                __IBExMsg("btn", this.Button.Folder[x].Hwnd , this.Idx , x)
                this.Button.Folder[x, "Path"] := folder1 ? folder1 : A_MyDocuments
            }
        }
        ;~ SET OK/CANCEL BUTTON(s)
        if cancel { ; Single-button(OK only)
            Gui, Add, Button , % "y+20 w" (w := 90) " x" (((w1+wSub)/2)-(w/2)) " h25", OK
            this.Button.OK["Hwnd"] := this.GetCtrl(), this.Button.Cancel["Hwnd"] := false ; Store Hwnd
            this.ILButton(this.Button.OK.Hwnd, "comres.dll:8", 16, 16, 0) ; Set button image
            __IBExMsg("btn", this.Button.OK.Hwnd , this.Idx , 0)
        } else { ; Default: 2 buttons(OK & Cancel)
            Gui, Add, Button, % "y+20 w" (w := 90) " x" (((w1+wSub)/2)-(((w*2) +10)/2)) " h25" , OK
            hBtn1 := this.GetCtrl()
            Gui, Add, Button, x+10 yp wp hp, Cancel
            hBtn2 :=  this.GetCtrl()
            for a, b in [8, 10] {
                this.Button[a == 1 ? "OK" : "Cancel", "Hwnd"] := hBtn%a%
                this.ILButton(hBtn%a%, "comres.dll:" b, 16, 16, 0, "2,2,0,2")
                __IBExMsg("btn", hBtn%a% , this.Idx , 0)
            }
        }
    }

    Destroy() {
        Gui, % this.Hwnd ":Destroy"
        this.Message(0x112, false, 0x0111, false) ; WM_COMMAND:=0x0111 | WM_SYSCOMMAND:=0x112
        __IBExMsg("del", this.Idx)
    }

    EventHandler(event:=1, param:="") {
        ; MPV - setting default values because of #warn
        static e := {0: "OK", 1: "Cancel", 2: "Close", 3: "Timeout"} , rv := "" , ev := ""
        if (event <= 3) {
            for a, b in this.Edit {
                GuiControlGet, f, % this.Hwnd ":", % b
                this.Output[a] := f
            }
            ev := e[event]
            ErrorLevel := ev
            if (axn := this.Action[ev]) {
                Gui , % this.Hwnd ":Cancel"
                if (IsLabel(axn) && !IsFunc(axn))
                    gosub % axn
                else if (IsFunc(axn) && !IsLabel(axn)) {
                    if ((fn := Func(axn)).MaxParams >= 2 && fn.MinParams <= 2)
                        rv := fn.([this.Output]*)
                } else if (IsLabel(axn) && IsFunc(axn))
                    gosub % axn
            }
            this.Destroy()
        }
        if (event == 4) { ; Select file button
            Gui , % this.Hwnd ":+OwnDialogs"
            FileSelectFile, file ,, % this.Button.File[param].Path, % "Select File - " this.Title, % this.Button.File[param].Filter
            if !ErrorLevel
                GuiControl, % this.Hwnd ":", % this.Edit[param] , % file
        }
        if (event == 5) { ; Select folder button
            Gui , % this.Hwnd ":+OwnDialogs"
            FileSelectFolder, folder , % this.Button.Folder[param].Path,, % "Select Folder - " this.Title
            if !ErrorLevel
                GuiControl, % this.Hwnd ":", % this.Edit[param] , % folder
        }
        if (event == 6) {
            elrv := []
            elrv["retval"] := rv , elrv["event"] := ev
            rv := "" , ev := ""
            return elrv
        }
    }

    Message(params*) {
        static fn := [] , fnc := "__IBExMsg"
        for a, b in params {
            if Mod(a, 2) {
                f := OnMessage(b, (m := params[a+1]) ? fnc : (fn.HasKey(b) ? fn[b] : ""))
                if (f && f <> fnc && m)
                    fn[b] := f
            }
        }
    }

    Split(str, delim:="|") {
        split := []
        Loop, Parse, str, % delim, % A_Space A_Tab
            split[A_Index] := A_LoopField
        return split
    }

    GetCtrl(idx:=0, option:="Hwnd") { ; idx:=0("last control added") option:=[Hwnd, ClassNN]
        cmd := {Hwnd: "ControlListHwnd", ClassNN: "ControlList"}
        pdhw := A_DetectHiddenWindows
        DetectHiddenWindows, On
        WinGet, Ctrls, % cmd[option], % "ahk_id " this.Hwnd
        DetectHiddenWindows, % pdhw
        c := this.Split(Ctrls, "`n")
        return c[!idx ? c.MaxIndex() : idx]
        ;~ if RegExMatch(Ctrls, "[^\n]*$", ctrl)
            ;~ return ctrl
    }

    Cue(lpMultiByteStr, idx:=0) {
        if A_IsUnicode
            WideCharStr := lpMultiByteStr
        else {
            nSize := DllCall("MultiByteToWideChar", "UInt", (CP_ACP := 0), "Uint", 0, (PtrType := (A_PtrSize=8) ? "Ptr" : "UInt", lpMultiByteStr, "Int"), -1, "UInt", 0, "Int", 0)
            VarSetCapacity(WideCharStr, nSize*2, 0)
            DllCall("MultiByteToWideChar", "UInt", CP_ACP, "UInt", 0, PtrType, lpMultiByteStr, "Int", nSize, "Str", WideCharStr, "Int", nSize)
        }
        SendMessage, 0x1501, true, &WideCharStr,, % "ahk_id " this.Edit[idx ? idx : this.Edit.MaxIndex()] ; EM_SETCUEBANNER
        return ErrorLevel
    }

    ToolTip(hCtrl:="", text:="", modify:=0) {
        ; MPV - setting default values because of #warn
        static hTT :=0 , hGui := 0, Ptr := 0
        hParent := DllCall("GetParent", "Ptr", hCtrl)
        if (hParent <> hGui) {
        ;~ If (!hTT) {
            ;~ hGui := DllCall("GetParent", "Ptr", hCtrl)
            hGui := hParent
            hTT := DllCall("CreateWindowEx", "Uint", 0, "Str", "TOOLTIPS_CLASS32", "Uint", 0, "Uint", 2147483648 | 3, "Uint", -2147483648
                , "Uint", -2147483648, "Uint", -2147483648, "Uint", -2147483648, "Uint", hGui, "Uint", 0, "Uint", 0, "Uint", 0)
            Ptr := (A_PtrSize ? "Ptr" : "UInt") ; , DllCall("uxtheme\SetWindowTheme","Uint",hTT,Ptr,0,"UintP",0)    ; TTM_SETWINDOWTHEME
        }
        Varsetcapacity(TInfo, 44, 0), Numput(44, TInfo), Numput(1|16, TInfo, 4), Numput(hGui, TInfo, 8), Numput(hCtrl, TInfo, 12), Numput(&text, TInfo, 36)
        !modify ? (DllCall("SendMessage", Ptr, hTT, "Uint", 1028, Ptr, 0, Ptr, &TInfo, Ptr)) ; TTM_ADDTOOL := 1028 (add a tool and assign to control)
            . (DllCall("SendMessage", Ptr, hTT, "Uint", 1048, Ptr, 0, Ptr, A_ScreenWidth)) ; TTM_SETMAXTIPWIDTH := 1048 (Multi-line tooltip)
        DllCall("SendMessage", Ptr, hTT, "UInt", (A_IsUnicode ? 0x439 : 0x40c), Ptr, 0, Ptr, &TInfo, Ptr) ; TTM_UPDATETIPTEXT (OLD_MSG:=1036) (Modify Tooltip text)
    }

    BorderSize(hwnd) {
        ; MPV - setting default values because of #warn
        pt := 0
        ClientX := 0
        ClientY := 0
        WinGetPos, WinX, WinY, WinW, WinH, ahk_id %hwnd%
        VarSetCapacity(rc, 16) , DllCall("GetClientRect", "uint", hwnd, "uint", &rc) , ClientW := NumGet(rc, 8, "int") , ClientH := NumGet(rc, 12, "int")
        NumPut(ClientX, pt, 0) , NumPut(ClientY, pt, 4) , VarSetCapacity(pt, 16) , DllCall("ClientToScreen", "uint", hwnd, "uint", &pt)
        ClientX := NumGet(pt, 0, "int") , ClientY := NumGet(pt, 4, "int") , Top := ClientY - WinY , Left := ClientX - WinX
        Bottom := WinH - ClientH - Top , Right := WinW - ClientW - Left
        return {L: Left, R: Right, T: Top, B: Bottom}
    }

    /*
    Title: ILButton
    Version: 1.1
    Author: tkoi <http://www.autohotkey.net/~tkoi>
    License: GNU GPLv3 <http://www.opensource.org/licenses/gpl-3.0.html>

    Function: ILButton()
        Creates an imagelist and associates it with a button.
    Parameters:
        hBtn   - handle to a buttton
        images - a pipe delimited list of images in form "file:zeroBasedIndex"
                   - file must be of type exe, dll, ico, cur, ani, or bmp
                   - there are six states: normal, hot (hover), pressed, disabled, defaulted (focused), and stylushot
                       - ex. "normal.ico:0|hot.ico:0|pressed.ico:0|disabled.ico:0|defaulted.ico:0|stylushot.ico:0"
                   - if only one image is specified, it will be used for all the button's states
                   - if fewer than six images are specified, nothing is drawn for the states without images
                   - omit "file" to use the last file specified
                       - ex. "states.dll:0|:1|:2|:3|:4|:5"
                   - omitting an index is the same as specifying 0
                   - note: within vista's aero theme, a defaulted (focused) button fades between images 5 and 6
        cx     - width of the image in pixels
        cy     - height of the image in pixels
        align  - an integer between 0 and 4, inclusive. 0: left, 1: right, 2: top, 3: bottom, 4: center
        margin - a comma-delimited list of four integers in form "left,top,right,bottom"

    Notes:
        A 24-byte static variable is created for each IL button
        Tested on Vista Ultimate 32-bit SP1 and XP Pro 32-bit SP2.

    Changes:
      v1.1
        Updated the function to use the assume-static feature introduced in AHK version 1.0.48
    */

    ILButton(hBtn, images, cx=16, cy=16, align=4, margin="1,1,1,1") { ; http://www.autohotkey.com/board/topic/37147-ilbutton-image-buttons-with-text-states-alignment/page-3
        static
        static i = 0
        local himl, v0, v1, v2, v3, ext, hbmp, hicon := 0
        i ++

        himl := DllCall("ImageList_Create", "Int",cx, "Int",cy, "UInt",0x20, "Int",1, "Int",5, "UPtr")
        Loop, Parse, images, |
        {
            Pos := InStr(A_LoopField, ":", false, 3)
            if(pos)
            {
                v1 := SubStr(A_LoopField, 1, pos - 1)
                v2 := SubStr(A_LoopField, pos + 1)
            }
            else
                v1 := A_LoopField
            SplitPath, v1, , , ext
            if(ext = "bmp")
            {
                hbmp := DllCall("LoadImage", "UInt",0, "Str",v1, "UInt",0, "UInt",cx, "UInt",cy, "UInt",0x10, "UPtr")
                DllCall("ImageList_Add", "Ptr",himl, "Ptr",hbmp, "Ptr",0)
                DllCall("DeleteObject", "Ptr", hbmp)
            }
            else
            {
                DllCall("PrivateExtractIcons", "Str",v1, "Int",v2, "Int",cx, "Int",cy, "PtrP",hicon, "UInt",0, "UInt",1, "UInt",0)
                DllCall("ImageList_AddIcon", "Ptr",himl, "Ptr",hicon)
                DllCall("DestroyIcon", "Ptr", hicon)
            }
        }
        ; Create a BUTTON_IMAGELIST structure
        VarSetCapacity(struct%i%, A_PtrSize + (5 * 4) + (A_PtrSize - 4), 0)
        NumPut(himl, struct%i%, 0, "Ptr")
        Loop, Parse, margin, `,
        NumPut(A_LoopField, struct%i%, A_PtrSize + ((A_Index - 1) * 4), "Int")
        NumPut(align, struct%i%, A_PtrSize + (4 * 4), "UInt")
        ; BCM_FIRST := 0x1600, BCM_SETIMAGELIST := BCM_FIRST + 0x2
        PostMessage, 0x1602, 0, &struct%i%, , ahk_id %hBtn%
        Sleep 1 ; workaround for a redrawing problem on WinXP
    }
}

__IBExMsg(wParam, lParam, msg:="", hwnd:="") {
    static ix := [] , i := 0 , btn := [] , WM_COMMAND := 0x0111 , WM_SYSCOMMAND := 0x112 , BN_CLICKED := 0x0000 , SC_CLOSE := 0xF060
    if (wParam == "new" && IsObject(lParam))
        ix[i += 1] := lParam
    if (wParam == "del") { ; Remove stored info in object upon calling Destroy() method
        keys := []
        for x, y in btn
            if (y.o == lParam)
                keys.Insert(x)
        for a, b in keys
            if btn.HasKey(b)
                btn.Remove(b, "")
    }
    if (wParam == "btn")
        btn[lParam] := {o: msg , b: hwnd}
    if (msg == WM_COMMAND) {
        lo := wParam & 0xFFFF
        hi := wParam >> 16
        if (hi == BN_CLICKED) {
            if btn.HasKey(lParam) {
                j := btn[lParam].o , k := btn[lParam].b
                if (lParam == ix[j].Button.OK.Hwnd) ; Button OK
                    ix[j].EventHandler(0)
                if (lParam == ix[j].Button.Cancel.Hwnd) ; Button Cancel
                    ix[j].EventHandler(1)
                if (lParam == ix[j].Button.File[k].Hwnd) ; Browse File
                    ix[j].EventHandler(4, k)
                if (lParam == ix[j].Button.Folder[k].Hwnd) ; Browse Folder
                    ix[j].EventHandler(5, k)
            }
        }
    }
    if (msg == WM_SYSCOMMAND && wParam == SC_CLOSE) {
        Loop, % ix.MaxIndex()
            if (hwnd == ix[A_Index].Hwnd)
                ix[A_Index].EventHandler(2)
    }
}

Hopefully, this helps someone else.

 

Mike V.



MikeV
  • Members
  • 19 posts
  • Last active: Sep 13 2015 09:08 PM
  • Joined: 03 Jun 2013

Here is the latest (and hopefully last?) two issues I have found with this script...  The "tip" does not seem to be working.  I have created a simple demo using multiple input controls.  My demo script is below (which includes the script from the previous message):

#SingleInstance force
#Warn

#f10::
    ; can use either the pipe-delimited string or the array
    inputItems := "Normal Input:|Multi-line Input:|lower only:|UPPER ONLY:|Numbers Only:|Password:|File Only:|Folder Only:"
    inputArray := ["Normal Input:", "Multi-line Input:", "lower only:", "UPPER ONLY:", "Numbers Only:", "Password:", "File Only:", "Folder Only:"]
    title := "Multi-input Demo"
    opts := "w600 Font(Segoe UI|s9 c0F0F80)"
    inputFont := " Font(Calibri|s10 cBlack Normal)"
    inputNormal := "R1 Default(Just a simple sentence.) Tip(111)" . inputFont
    inputMultiline := "R3 -Wrap HScroll Tip(This control accepts multiple lines of text)" . inputFont
    inputLower := "R1 Lower Tip(333)" . inputFont
    inputUpper := "R1 Upper Tip(444)" . inputFont
    inputNumber := "R1 Number Tip(555)" . inputFont
    inputPassword := "R1 Password Tip(666)" . inputFont
    inputFile := "R1 Cue(Select a text file...) File(|Text File:*.txt) Tip(777)" . inputFont
    inputFolder := "R1 Cue(Select a folder...) Folder(Computer) Tip(888)" . inputFont

    ibx := new InputBoxEx(inputItems, title, opts, "", inputNormal, inputMultiline, inputLower, inputUpper, inputNumber, inputPassword, inputFile, inputFolder)
    ibx.show()
    if (ErrorLevel == "OK") {
        askOK(ibx.Output)
    }
    else if (ErrorLevel == "Cancel") {
        askCancel()
    }
    else if (ErrorLevel == "Close") {
        askClose()
    }
    else if (ErrorLevel == "Timeout") {
        askTimeout()
    }
    return

askOK(values) {
    message(toString(values))
}

askCancel() {
    message("CANCEL was clicked.")
}

askClose() {
    message("CLOSE was clicked.")
}

askTimeout() {
    message("Timeout occurred.")
}

message(msg) {
    MsgBox % msg
}

toString(array, depth:=6, indent:="") {
    static EOL := "`n"
    result := ""
    if (IsObject(array)) {
        for key, value in array {
            result .= "`t" . indent . key
            if (IsObject(value) && depth > 1) {
                result .= EOL . toString(value, depth - 1, indent . "`t")
            }
            else {
                result .= "`t= [" . value . "]" . EOL
            }
        }
    }
    else {
        result := array
    }
    return result
}

/*
CLASS: InputBoxEx
DESCRIPTION: Provides users an easy way to create customized/dynamic InputBoxes.
REQUIREMENT(s): AutoHotkey v. 1.1.9.x (I'm using the ":=" operator for optional parameters in functions/methods)
FEATURES: All options available in the standard AHK InputBox+more(see below)
    - Option to create multiple input fields
    - GUI can be customized [color and font]
    - Each input field can be customized
        * prompt
        * font[name,color,size,style]
        * no. of rows[single or multiline]
        * input type[numbers only]
        * display type[password, uppercase, lowercase]
        * attach tooltip
        * set cue banner
        * add button(s) that allow users to select a file or a folder
    - Option to remove or show the "Cancel" button
    - Option to associate a label or a function with a particular event.
        Event types:
            * OK - User clicked the OK button
            * Cancel - User clicked the Cancel button
            * Close - User dismissed the InputBox via the Close button
            * Timeout - InputBox timed out
    - More features are to be added
USAGE: Script designer need only call two methods
    - Use #Include InputBoxEx.ahk or #Include <InputBoxEx> (if script is located in your lib folder) or copy code and paste it on your script
    - Instantiate the class using the "new" keyword and pass the appropriate parameter(s)
        e.g. ibxObj := new InputBoxEx(params*) -for parameters, see documentation for __New() method-
    - Then show the InputBox using the Show() method
        e.g. ibxObj.Show(param) -for parameters, see documentation for Show() method-
NOTES:
    Behavior:
        - It behaves pretty much the same as the standard AHK InputBox.
        - When the user calls the Show() method, the lines below it will not be executed until the InputBox is dismissed (via OK, Cancel, Close or Timeout). ***similar to MsgBox and AHK InputBox
        - If an associated action(label or function) is assigned to an event, that subroutine will be executed first then script will continue at the lines(if any) after the Show() method.
        - Text(output) that the user has input can be retieved via the ibxObj.Output[x](which is an object) property, where x is the index of a particualr input field.
            e.g. Field1 := ibxObj.Output.1 (or ibxObj.Output[1]) and so on...
        - When the user dismisses the InputBox, ErrorLevel is set to either of the following values(depending on the event): "OK", "Cancel", "Close", "Timeout"
            *** if an event has an associated action(laber or function), and if within that subroutine ErrorLevel's value is altered by a command(e.g. Run), upon return from that subroutine,
            ErrorLevel is reset back to the "event" type. This allows users to retrieve the event type via ErrorLevel on the lines after the Show() method call.
        - When associating a function with an event, the function should have atleast 1 parameter & the minimum required parameter(s) should be equal to or less than 1.
            *** The class passes an array/object to the function's 1st parameter which contains whatever the user has input.
            *** The associated function's return value (if any) is returned by the Show() method. e.g.: IBX_EventRetVal := ibxObj.Show(param)
    Others/ToDo's/Issues:
        - [issue] Currently, InputBoxEx is not manually resizable (will work on this)
        - [issue] Button images might appear different on other systems. Tested only on Win7, Dll files have different icon nos on different systems (will fix this)
        - [intentional] InputBoxEx height is not configurable, this is done automatically.
        - [intentional] Unlike input field(s) whose font can be set individually, "prompt" font is uniform and follows the whatever is set by the user for the GUI font (might allow customization)
        - [todo] Implement option to allow users to add a ComboBox and/or a DateTime control instead of just an Edit control as an input field.
        - [todo] Option to add an UpDown control and use the input field as a buddy control.
        - [todo?] Might add option to allow users to add Checkboxes and Radio controls
        - [todo?] Option to add custom buttons with custom actions
CREDITS:
    Credits to tkoi and corrupt for the ILButton() function. (http://www.autohotkey.com/board/topic/37147-ilbutton-image-buttons-with-text-states-alignment/)
    Credits to sbc for the GetBorderSize() function. (http://www.autohotkey.com/board/topic/60410-function-getbordersize/)
    Credits to art for AddToolTip() function, I altered it a bit though. (http://www.autohotkey.com/board/topic/27670-add-tooltips-to-controls/page-2#entry431059)
*/

class InputBoxEx
{

    /*
    METHOD: __New()
    DESCRIPTION: Constructor for the class
    PARAMETER(s):
        prompt(required) - Text to display, a message to the user indicating what kind of input is expected
                                            User can pass a string or an object/array. If string, it can be a pipe-delimited list of text items to display, no. of items indicates the no. of input field(s)
                                            Example: (String) - "Prompt One|Prompt Two|Prompt Three" ; (Object/Array) - ["Prompt One", "Prompt Two" , "Prompt Three"]

        title(optional) - Title of the InputBox, If blank or omitted, it defaults to the name of the script.

        optionsA(optional) - InputBox GUI options. String containing any(or none) of following(space-delimited). If blank, default settings will be used(see description below)
                                            Example: "w450 c0xFFFFFF Font(Segoe UI|s9 cRed)"
            Wn - Width of the InputBox, where n is the amount in pixels. If omitted, it defaults to 375(same with AHK standard InputBox)
            C - GUI color. Specify the letter "C" followed immediately by a color name(see color chart in AHK help file) or RGB value (the 0x prefix is optional).
            Font(FontName|FontOptions) or F(FontName|FontOptions) - GUI font. Specify the word "Font" or the letter "F" (not case-sensitive) followed by immediately by a string enclosed in parentheses(open and close)
                                                                                                                        String format should be: '(Font Name|Font Options)' (note pipe delimiter and parentheses), where "Font Name" is the name of the font
                                                                                                                        and "Font Options" is a space-delimited string containing standard font options(see AHK "Gui, Font" command for a list of options. e.g.: "cRed Italic s9")
                                                                                                                        Example: Font(Segoe UI|cRed s10 Italic Underline) ***note: this is a string not a function or expression, for easy parsing***
            -Cancel - Specify the word "-Cancel" to remove the "Cancel" button. InputBox will only have the "OK" button if this option is specified. Default is two buttons: OK and Cancel

        action(optional) - Label or Funcion to call for each InputBoxEx event. There are 4 events: OK, Cancel, Close, Timeout. (Self-explanatory)
                                        User can pass a string or an object/array. If string, it should be a pipe-delimited list(max=4 items) of labels or functions names where item1 corresponds to the "OK" event
                                        , item2 to the "Cancel" event, item3 to the "Close" event and item4 to the "Timeout" event. If no label or function is specified for a particular event , no action is taken.
                                        When InputBoxEx is dismissed via any of the events stated above, user can retrieve the event type/name via the obj.Event property. (This is similar to ErrorLevel for the standard AHK InputBox)
                                        Example: "LabelOK|LabelCancel|LabelClose|LabelTimeout" or if Array/object: ["LabelOK", "LabelCancel", "LabelClose", "LabelTimeout"] ***see notes reagrding InputBoxEx behavior***

        optionsB(variadic) - InputBox input field option(s). String containing any(or none) of following(space-delimited). If blank, default settings will be used(see description below).
                                            Number of parameters must be equal or less than the number of items in the "prompt" parameter. Each parameter corresponds to each prompt/field.
                                            Example: "R5 Default(This is the default text) Folder(C:\Windows\System32) Font(Consolas|s9 cBlue Italic) File(C:\Users\Username\Pictures|Image Files:*.jpg; *.png; *.bmp)"
            R - Number of rows of the input field(Edit control). Specify the letter "R" followed immediately by a number. If omitted it defaults to 1. (e.g.: R5 - five rows)
            Password - Hides the user's input. Similar to "Hide" option for the standard AHK InputBox. This option has no effect for multi-line input fields
            Number - Prevents the user from typing anything other than digits into the field.
            Uppercase or Upper - The characters typed by the user are automatically converted to uppercase.
            Lowercase or Lower - The characters typed by the user are automatically converted to lowercase.
            -Wrap - Turns off word-wrapping in a multi-line input field.
            HScroll - Adds a horizontal scrollbar, useful for input fields with the "-Wrap" option.
            Font(FontName|FontOptions) or F(FontName|FontOptions) - Input field font. For usage/format, see same option for "optionsA" parameter above.
            Default(DefaultText) - A string that will appear in the InputBox's edit field when the dialog first appears.
                                                    Specify the word "Default" followed immediately by a string enclosed in parentheses(open and close)
                                                    Example: Default(This is the default text.) ***note: this is a string not a function or expression, for easy parsing***
            Cue(CueText) - Sets the textual cue, or tip, that is displayed by the input field to prompt the user for information.
                                        Specify the word "Cue" followed immediately by a string enclosed in parentheses(open and close)
                                        Example: Cue(This is a cue banner) ***note: this is a string not a function or expression, for easy parsing***
                                        This option has no effect for multi-line edit controls.
            Tip(TipText) - Sets/creates the tooltip that is displayed every time the mouse hovers over the input field/edit control.
                                    Specify the word "Tip" followed immediately by a string enclosed in parentheses(open and close)
                                    Example: Tip(This is a tooltip) ***note: this is a string not a function or expression, for easy parsing***
            File(RootDir|Filter) - Adds a button to the right of the input field that when pressed, displays a standard dialog that allows the user to select a file.
                                                Specify the word "File" followed immediately by a string enclosed in parentheses(open and close).
                                                String format should be: '(RootDir|Filter)' (note pipe delimiter and parentheses), where RootDir is the root(starting) directory and filter indicates which types of files are shown by the dialog.
                                                If 'RootDir' is omitted (e.g. by just specifying 'File(|Filter)', **note a pipe "|" is still specified or else 'Filter' will be assumed as 'RootDir'**) it defaults to the user's "My Documents" folder
                                                The button is a 25x25 px sized button with a "search folder" icon. A tooltip with the text "Browse file" is displayed when the mouse hovers over the button.
                                                ***FILTER SYNTAX: [FileType:*FileExt1; *FileExt2] (note colon[:] delimiter between "FileType" and "FileExt" and also the semi-colon[;]+space that separates each file extension). If omitted, the filter defaults to All Files (*.*).
                                                ***see AHK "FileSelectFile" command for more info on RootDir and Filter***
                                                Example: File(C:\Windows|Audio:*.wav; *.mp2; *.mp3) ***note: this is a string not a function or expression, for easy parsing***
            Folder(RootDir) - Adds a button to the right of the input field that when pressed, displays a standard dialog that allows the user to select a folder. Path is displayed in the input field.
                                            Specify the word "Folder" followed immediately by a string enclosed in parentheses(open and close).
                                            String format should be: '(RootDir)' (note parentheses) , where RootDir is the root(starting) directory. If 'RootDir' is omitted (e.g. by just specifying 'Folder()') it defaults to the user's "My Documents" folder
                                            The button is a 25x25 px sized button with a "search folder" icon. A tooltip with the text "Browse folder" is displayed when the mouse hovers over the button.
                                            Example: Folder(C:\Windows) ***note: this is a string not a function or expression, for easy parsing***

    USAGE: Use the "new" keyword to create a class instance
        Syntax:
            ibxObj := new InputBoxEx(prompt, title, optionsA, action, optionsB*)
        Example:
            ibxObj := new InputBoxEx("Name:|Address:"
                                                            , "Contact Details"
                                                            , "w400 cWhite Font(Tahoma|s9 cMaroon)"
                                                            , "OK|Cancel|Close|Timeout"
                                                            , "Tip(Enter name) Font(Segoe UI|s9)"
                                                            , "R3 Cue(Address here) Font(Segoe UI|s9)")
    RETURN VALUE: A derived object
    */

    __New(prompt, title:="", optionsA:="", action:="", optionsB*) {
        static i := 0
        __IBExMsg("new", this)
        this.Prompt := [] , this.Title := (title ? title : A_ScriptName) , this.Action := [] , this.OptionsA := optionsA , this.OptionsB := optionsB
        , this.Button := {OK: [], Cancel: [], File : [], Folder: []} , this.Edit := []
        , this.Output := [] , this.Idx := i+=1
        for x, y in {prompt: prompt, action: action} {
            axn := ["OK", "Cancel", "Close", "Timeout"]
            if IsObject(y) {
                for k, v in y
                    this[x, ((x = "action") ? axn[k] : k)] := v
            } else {
                for a, b in this.Split(y)
                    this[x, ((x = "action") ? axn[a] : a)] := b
            }
        }
    }

    /*
    METHOD: Show()
    DESCRIPTION: Shows the InputBox
    PARAMATER(s):
        options(optional) - String containing any(or none) of following(space-delimited)
            xn - x-position where n is the amount in pixels
            yn - y-position where n is the amount in pixels
            tn - timeout , where n is the amount in milliseconds
    USAGE: ibxObj.Show("x10 y50 t5000")
    RETURN VALUE: If an event has an associated action, particularly a function, this method returns that function's return value(if any) otherwise none
    */

    Show(options:="") {
        static rxn := {x: "i)\s*\Kx\d+(?=\s*)", y: "i)\s*\Ky\d+(?=\s*)", t: "i)\s*\Kt(\d+)(?=\s*)"}
        ; MPV - setting default values because of #warn
        t := 0
        x := 0
        y := 0
        elrv := ""
        if options
            for a, b in rxn
                RegExMatch(options, b, %a%)
        pdhw := A_DetectHiddenWindows
        DetectHiddenWindows, On
        if WinExist("ahk_id " this.Hwnd)
            Gui, % this.Hwnd ":Show" , % (x ? (y ? x " " y : x) : (y ? y : "")) , % this.Title
        else {
            this.Create()
            , this.Show(options)
            , this.Message(0x112, true, 0x0111, true) ; WM_COMMAND:=0x0111 | WM_SYSCOMMAND:=0x112
            /*
            if t {
                ;~ start := A_TickCount
                ;~ while ((A_TickCount - start) <> t1) ;~ Sleep, % t1
                    ;~ continue
                ;~ this.EventHandler(3)
                WinWaitClose, % "ahk_id " this.Hwnd,, % t1/1000
                if ErrorLevel
                    this.EventHandler(3)
            }
            */
            WinWaitClose, % "ahk_id " this.Hwnd,, % (t ? t1/1000 : "")
            if ErrorLevel ; in case timeout is specified
                this.EventHandler(3)
            Errorlevel := (elrv := this.EventHandler(6)).event ; set ErrorLevel to the event type
        }
        DetectHiddenWindows, % pdhw
        if elrv
            return elrv.retval
    }

    ;=================
    ;~ PRIVATE METHODS
    ;=================

    Create() {
        ; REGEX NEEDLES (for maintainability purposes, easier to add/remove/alter)
        static rxn := {color: "\s*(C|c)\K[^\s]*" ; Color
            , font: "i)\s*\K(f|font)\(([^\)]*)\)" ; Font
            , cancel: "i)\s*\K\-Cancel(?=\s*)" ; -Cancel
            , owner: "i)\s*\Kowner[^\s]*" ; Gui Owner
            , mws: "\s+" ; Multiple whitespace(s)
            , width: "i)\s*\Kw(\d+)(?=\s*)" ; Width
            , default: "i)\s*\Kdefault\(([^\)]*)\)" ; Default
            , cue: "i)\s*\Kcue\(([^\)]*)\)" ; Cue Banner
            , tip: "i)\s*\Ktip\(([^\)]*)\)" ; Tooltip
            , file: "i)\s*\Kfile\(([^\)]*)\)" ; File search
            , folder: "i)\s*\Kfolder\(([^\)]*)\)"
            , mid: "\(\K[^\)]*"}
        ;~ BUILD GUI
        Gui, New ; Create a nameless GUI to avoid conflict with existing GUI numbers | will identify it via Hwnd
        Gui , % "+LastFound" (RegExMatch(this.optionsA, rxn.owner, owner) ? " +" owner : "") ; GUI options
        this.Hwnd := WinExist() ; Store Hwnd
        optionsA := RegExMatch(this.OptionsA, rxn.font, fontA) ? RegExReplace(this.OptionsA, rxn.font, "") : this.OptionsA
        optionsA := RegExMatch(optionsA, rxn.cancel, cancel) ? RegExReplace(optionsA, rxn.cancel, "") : optionsA
        optionsA := Trim(RegExReplace(optionsA, rxn.mws, A_Space))
        ;~ SET COLOR
        if RegExMatch(optionsA, rxn.color, clr)
            Gui, Color , % clr
        ;~ SET GUI FONT
        if fontA {
            fA := this.Split(fontA2)
            Gui , Font , % Trim(fA.2), % Trim(fA.1)
        }
        Gui, Margin, 10, 10
        ;~ GET BORDER SIZE
        bdr := this.BorderSize(this.Hwnd)
        ;~ if !bdr
            ;~ bdr := this.BorderSize(this.Hwnd)
        ; MPV - setting default values because of #warn
        fontB := ""
        cue := ""
        default := ""
        eO := ""
        file := ""
        folder := ""
        o := ""
        r := ""
        tip := ""
        ;~ SET INPUT FIELD(s)
        for x, y in this.Prompt {
            pRow := this.Split(y, "`n").MaxIndex()
            ;~ SET PROMPT WIDTH - to automatically wrap long text
            wSub := 20+abs(bdr.L)+abs(bdr.R)
            pW := RegExMatch(optionsA, rxn.width, ibxW) ? (ibxW1-wSub) : (375-wSub)
            Gui , Add, Text , % "w" pW (x == 1 ? " Section" : " xs"), % y ; "r" pRow
            if this.OptionsB[x] {
                eO := this.OptionsB[x]
                for a, b in {fontB: rxn.font, default: rxn.default, cue: rxn.cue, tip: rxn.tip, file: rxn.file, folder: rxn.folder}
                    eO := RegExMatch(eO, b, %a%) ? RegExReplace(eO, b, "") : eO
            }
            eO := Trim(RegExReplace(eO, rxn.mws, A_Space))
            Loop, Parse, eO, % A_Space
                if A_LoopField in % (RegExMatch(eO, "i)r(\d+)", r) ? r "," : "") "Password,Number,Uppercase,Lowercase,Upper,Lower,-Wrap,HScroll"
                    o .= (A_LoopField = "upper" || A_LoopField = "lower") ? (A_LoopField "case ") : (A_LoopField  " ")
            eO := Trim(o) , o := ""
            if !r
                eO .= " r1" , r1 := 1
            ;~ SET INPUT FIELD WIDTH
            wSub := file ? 29+wSub : wSub
            wSub := folder ? (file ? (r1 <= 2 ? 27+wSub : wSub) : 29+wSub) : wSub
            eW := "w" (RegExMatch(optionsA, rxn.width, w) ? (w1 -= wSub) " " : (w1 := 375-wSub) " ") ; 20+n1+n2:=Border Size+Margin | 375:=Default width, same with AHK std InputBox
            Gui, Add, Edit, % eW " " eO " xm y+5", % default ? default1 : ""
            this.Edit[x] := (hEdit := this.GetCtrl()) ; Store Edit control(s) Hwnd
            ;~ SET INPUT FIELD FONT
            if fontB {
                fB := this.Split(fontB2)
                Gui , Font , % Trim(fB.2), % Trim(fB.1)
                GuiControl, Font, % this.Edit[x]
                Gui , Font ; Restore to default
                if fontA ; if GUI font is specified, restore back
                    Gui , Font , % Trim(fA.2), % Trim(fA.1)
            }
            ;~ SET CUE BANNER
            if (cue && r1 == 1)
                this.Cue(cue1)
            ;~ SET TOOLTIP
            if tip
                this.ToolTip(this.Edit[x], tip1)
            ;~ SET ADDITIONAL "BROWSE FILE/FOLDER" BUTTON(s) - if specified
            if file { ; Browse file button
                Gui , Add , Button , % "x+4 " (r1 <= 1 ? "yp-1 " : "yp ") "w25 h25"
                this.ILButton(this.Button.File[x, "Hwnd"] := this.GetCtrl(), "Shell32.dll:55", 16, 16, 4) ; need to check icon(s) in other systems (Win7 tested)
                this.ToolTip(this.Button.File[x].Hwnd, "Browse file")
                __IBExMsg("btn", this.Button.File[x].Hwnd , this.Idx , x)
                ;~ Set options (RootDir, filter)
                this.Button.File[x, "Path"] := this.Split(file1).1 ? this.Split(file1).1 : A_MyDocuments
                filter := this.Split(file1).2 ? RegExReplace(this.Split(file1).2, "\:", A_Space "(") : ""
                filter .= filter ? ")" : ""
                this.Button.File[x, "Filter"] := filter
            }
            if folder { ; Browse folder button
                Gui , Add , Button , % (file ? (r1 <= 2 ? "x+2 yp " : "xp y+1 ") : "x+4 " (r1 <= 1 ? "yp-1 " : "yp ")) "w25 h25"
                this.ILButton(this.Button.Folder[x, "Hwnd"] := this.GetCtrl(), "imageres.dll:204", 16, 16, 4)
                this.ToolTip(this.Button.Folder[x].Hwnd , "Browse folder")
                __IBExMsg("btn", this.Button.Folder[x].Hwnd , this.Idx , x)
                this.Button.Folder[x, "Path"] := folder1 ? folder1 : A_MyDocuments
            }
        }
        ;~ SET OK/CANCEL BUTTON(s)
        if cancel { ; Single-button(OK only)
            Gui, Add, Button , % "y+20 w" (w := 90) " x" (((w1+wSub)/2)-(w/2)) " h25", OK
            this.Button.OK["Hwnd"] := this.GetCtrl(), this.Button.Cancel["Hwnd"] := false ; Store Hwnd
            this.ILButton(this.Button.OK.Hwnd, "comres.dll:8", 16, 16, 0) ; Set button image
            __IBExMsg("btn", this.Button.OK.Hwnd , this.Idx , 0)
        } else { ; Default: 2 buttons(OK & Cancel)
            Gui, Add, Button, % "y+20 w" (w := 90) " x" (((w1+wSub)/2)-(((w*2) +10)/2)) " h25" , OK
            hBtn1 := this.GetCtrl()
            Gui, Add, Button, x+10 yp wp hp, Cancel
            hBtn2 :=  this.GetCtrl()
            for a, b in [8, 10] {
                this.Button[a == 1 ? "OK" : "Cancel", "Hwnd"] := hBtn%a%
                this.ILButton(hBtn%a%, "comres.dll:" b, 16, 16, 0, "2,2,0,2")
                __IBExMsg("btn", hBtn%a% , this.Idx , 0)
            }
        }
    }

    Destroy() {
        Gui, % this.Hwnd ":Destroy"
        this.Message(0x112, false, 0x0111, false) ; WM_COMMAND:=0x0111 | WM_SYSCOMMAND:=0x112
        __IBExMsg("del", this.Idx)
    }

    EventHandler(event:=1, param:="") {
        ; MPV - setting default values because of #warn
        static e := {0: "OK", 1: "Cancel", 2: "Close", 3: "Timeout"} , rv := "" , ev := ""
        if (event <= 3) {
            for a, b in this.Edit {
                GuiControlGet, f, % this.Hwnd ":", % b
                this.Output[a] := f
            }
            ev := e[event]
            ErrorLevel := ev
            if (axn := this.Action[ev]) {
                Gui , % this.Hwnd ":Cancel"
                if (IsLabel(axn) && !IsFunc(axn))
                    gosub % axn
                else if (IsFunc(axn) && !IsLabel(axn)) {
                    if ((fn := Func(axn)).MaxParams >= 2 && fn.MinParams <= 2)
                        rv := fn.([this.Output]*)
                } else if (IsLabel(axn) && IsFunc(axn))
                    gosub % axn
            }
            this.Destroy()
        }
        if (event == 4) { ; Select file button
            Gui , % this.Hwnd ":+OwnDialogs"
            FileSelectFile, file ,, % this.Button.File[param].Path, % "Select File - " this.Title, % this.Button.File[param].Filter
            if !ErrorLevel
                GuiControl, % this.Hwnd ":", % this.Edit[param] , % file
        }
        if (event == 5) { ; Select folder button
            Gui , % this.Hwnd ":+OwnDialogs"
            FileSelectFolder, folder , % this.Button.Folder[param].Path,, % "Select Folder - " this.Title
            if !ErrorLevel
                GuiControl, % this.Hwnd ":", % this.Edit[param] , % folder
        }
        if (event == 6) {
            elrv := []
            elrv["retval"] := rv , elrv["event"] := ev
            rv := "" , ev := ""
            return elrv
        }
    }

    Message(params*) {
        static fn := [] , fnc := "__IBExMsg"
        for a, b in params {
            if Mod(a, 2) {
                f := OnMessage(b, (m := params[a+1]) ? fnc : (fn.HasKey(b) ? fn[b] : ""))
                if (f && f <> fnc && m)
                    fn[b] := f
            }
        }
    }

    Split(str, delim:="|") {
        split := []
        Loop, Parse, str, % delim, % A_Space A_Tab
            split[A_Index] := A_LoopField
        return split
    }

    GetCtrl(idx:=0, option:="Hwnd") { ; idx:=0("last control added") option:=[Hwnd, ClassNN]
        cmd := {Hwnd: "ControlListHwnd", ClassNN: "ControlList"}
        pdhw := A_DetectHiddenWindows
        DetectHiddenWindows, On
        WinGet, Ctrls, % cmd[option], % "ahk_id " this.Hwnd
        DetectHiddenWindows, % pdhw
        c := this.Split(Ctrls, "`n")
        return c[!idx ? c.MaxIndex() : idx]
        ;~ if RegExMatch(Ctrls, "[^\n]*$", ctrl)
            ;~ return ctrl
    }

    Cue(lpMultiByteStr, idx:=0) {
        if A_IsUnicode
            WideCharStr := lpMultiByteStr
        else {
            nSize := DllCall("MultiByteToWideChar", "UInt", (CP_ACP := 0), "Uint", 0, (PtrType := (A_PtrSize=8) ? "Ptr" : "UInt", lpMultiByteStr, "Int"), -1, "UInt", 0, "Int", 0)
            VarSetCapacity(WideCharStr, nSize*2, 0)
            DllCall("MultiByteToWideChar", "UInt", CP_ACP, "UInt", 0, PtrType, lpMultiByteStr, "Int", nSize, "Str", WideCharStr, "Int", nSize)
        }
        SendMessage, 0x1501, true, &WideCharStr,, % "ahk_id " this.Edit[idx ? idx : this.Edit.MaxIndex()] ; EM_SETCUEBANNER
        return ErrorLevel
    }

    ToolTip(hCtrl:="", text:="", modify:=0) {
        ; MPV - setting default values because of #warn
        static hTT :=0 , hGui := 0, Ptr := 0
        hParent := DllCall("GetParent", "Ptr", hCtrl)
        if (hParent <> hGui) {
        ;~ If (!hTT) {
            ;~ hGui := DllCall("GetParent", "Ptr", hCtrl)
            hGui := hParent
            hTT := DllCall("CreateWindowEx", "Uint", 0, "Str", "TOOLTIPS_CLASS32", "Uint", 0, "Uint", 2147483648 | 3, "Uint", -2147483648
                , "Uint", -2147483648, "Uint", -2147483648, "Uint", -2147483648, "Uint", hGui, "Uint", 0, "Uint", 0, "Uint", 0)
            Ptr := (A_PtrSize ? "Ptr" : "UInt") ; , DllCall("uxtheme\SetWindowTheme","Uint",hTT,Ptr,0,"UintP",0)    ; TTM_SETWINDOWTHEME
        }
        Varsetcapacity(TInfo, 44, 0), Numput(44, TInfo), Numput(1|16, TInfo, 4), Numput(hGui, TInfo, 8), Numput(hCtrl, TInfo, 12), Numput(&text, TInfo, 36)
        !modify ? (DllCall("SendMessage", Ptr, hTT, "Uint", 1028, Ptr, 0, Ptr, &TInfo, Ptr)) ; TTM_ADDTOOL := 1028 (add a tool and assign to control)
            . (DllCall("SendMessage", Ptr, hTT, "Uint", 1048, Ptr, 0, Ptr, A_ScreenWidth)) ; TTM_SETMAXTIPWIDTH := 1048 (Multi-line tooltip)
        DllCall("SendMessage", Ptr, hTT, "UInt", (A_IsUnicode ? 0x439 : 0x40c), Ptr, 0, Ptr, &TInfo, Ptr) ; TTM_UPDATETIPTEXT (OLD_MSG:=1036) (Modify Tooltip text)
    }

    BorderSize(hwnd) {
        ; MPV - setting default values because of #warn
        pt := 0
        ClientX := 0
        ClientY := 0
        WinGetPos, WinX, WinY, WinW, WinH, ahk_id %hwnd%
        VarSetCapacity(rc, 16) , DllCall("GetClientRect", "uint", hwnd, "uint", &rc) , ClientW := NumGet(rc, 8, "int") , ClientH := NumGet(rc, 12, "int")
        NumPut(ClientX, pt, 0) , NumPut(ClientY, pt, 4) , VarSetCapacity(pt, 16) , DllCall("ClientToScreen", "uint", hwnd, "uint", &pt)
        ClientX := NumGet(pt, 0, "int") , ClientY := NumGet(pt, 4, "int") , Top := ClientY - WinY , Left := ClientX - WinX
        Bottom := WinH - ClientH - Top , Right := WinW - ClientW - Left
        return {L: Left, R: Right, T: Top, B: Bottom}
    }

    /*
    Title: ILButton
    Version: 1.1
    Author: tkoi <http://www.autohotkey.net/~tkoi>
    License: GNU GPLv3 <http://www.opensource.org/licenses/gpl-3.0.html>

    Function: ILButton()
        Creates an imagelist and associates it with a button.
    Parameters:
        hBtn   - handle to a buttton
        images - a pipe delimited list of images in form "file:zeroBasedIndex"
                   - file must be of type exe, dll, ico, cur, ani, or bmp
                   - there are six states: normal, hot (hover), pressed, disabled, defaulted (focused), and stylushot
                       - ex. "normal.ico:0|hot.ico:0|pressed.ico:0|disabled.ico:0|defaulted.ico:0|stylushot.ico:0"
                   - if only one image is specified, it will be used for all the button's states
                   - if fewer than six images are specified, nothing is drawn for the states without images
                   - omit "file" to use the last file specified
                       - ex. "states.dll:0|:1|:2|:3|:4|:5"
                   - omitting an index is the same as specifying 0
                   - note: within vista's aero theme, a defaulted (focused) button fades between images 5 and 6
        cx     - width of the image in pixels
        cy     - height of the image in pixels
        align  - an integer between 0 and 4, inclusive. 0: left, 1: right, 2: top, 3: bottom, 4: center
        margin - a comma-delimited list of four integers in form "left,top,right,bottom"

    Notes:
        A 24-byte static variable is created for each IL button
        Tested on Vista Ultimate 32-bit SP1 and XP Pro 32-bit SP2.

    Changes:
      v1.1
        Updated the function to use the assume-static feature introduced in AHK version 1.0.48
    */

    ILButton(hBtn, images, cx=16, cy=16, align=4, margin="1,1,1,1") { ; http://www.autohotkey.com/board/topic/37147-ilbutton-image-buttons-with-text-states-alignment/page-3
        static
        static i = 0
        local himl, v0, v1, v2, v3, ext, hbmp, hicon := 0
        i ++

        himl := DllCall("ImageList_Create", "Int",cx, "Int",cy, "UInt",0x20, "Int",1, "Int",5, "UPtr")
        Loop, Parse, images, |
        {
            Pos := InStr(A_LoopField, ":", false, 3)
            if(pos)
            {
                v1 := SubStr(A_LoopField, 1, pos - 1)
                v2 := SubStr(A_LoopField, pos + 1)
            }
            else
                v1 := A_LoopField
            SplitPath, v1, , , ext
            if(ext = "bmp")
            {
                hbmp := DllCall("LoadImage", "UInt",0, "Str",v1, "UInt",0, "UInt",cx, "UInt",cy, "UInt",0x10, "UPtr")
                DllCall("ImageList_Add", "Ptr",himl, "Ptr",hbmp, "Ptr",0)
                DllCall("DeleteObject", "Ptr", hbmp)
            }
            else
            {
                DllCall("PrivateExtractIcons", "Str",v1, "Int",v2, "Int",cx, "Int",cy, "PtrP",hicon, "UInt",0, "UInt",1, "UInt",0)
                DllCall("ImageList_AddIcon", "Ptr",himl, "Ptr",hicon)
                DllCall("DestroyIcon", "Ptr", hicon)
            }
        }
        ; Create a BUTTON_IMAGELIST structure
        VarSetCapacity(struct%i%, A_PtrSize + (5 * 4) + (A_PtrSize - 4), 0)
        NumPut(himl, struct%i%, 0, "Ptr")
        Loop, Parse, margin, `,
        NumPut(A_LoopField, struct%i%, A_PtrSize + ((A_Index - 1) * 4), "Int")
        NumPut(align, struct%i%, A_PtrSize + (4 * 4), "UInt")
        ; BCM_FIRST := 0x1600, BCM_SETIMAGELIST := BCM_FIRST + 0x2
        PostMessage, 0x1602, 0, &struct%i%, , ahk_id %hBtn%
        Sleep 1 ; workaround for a redrawing problem on WinXP
    }
}

__IBExMsg(wParam, lParam, msg:="", hwnd:="") {
    static ix := [] , i := 0 , btn := [] , WM_COMMAND := 0x0111 , WM_SYSCOMMAND := 0x112 , BN_CLICKED := 0x0000 , SC_CLOSE := 0xF060
    if (wParam == "new" && IsObject(lParam))
        ix[i += 1] := lParam
    if (wParam == "del") { ; Remove stored info in object upon calling Destroy() method
        keys := []
        for x, y in btn
            if (y.o == lParam)
                keys.Insert(x)
        for a, b in keys
            if btn.HasKey(b)
                btn.Remove(b, "")
    }
    if (wParam == "btn")
        btn[lParam] := {o: msg , b: hwnd}
    if (msg == WM_COMMAND) {
        lo := wParam & 0xFFFF
        hi := wParam >> 16
        if (hi == BN_CLICKED) {
            if btn.HasKey(lParam) {
                j := btn[lParam].o , k := btn[lParam].b
                if (lParam == ix[j].Button.OK.Hwnd) ; Button OK
                    ix[j].EventHandler(0)
                if (lParam == ix[j].Button.Cancel.Hwnd) ; Button Cancel
                    ix[j].EventHandler(1)
                if (lParam == ix[j].Button.File[k].Hwnd) ; Browse File
                    ix[j].EventHandler(4, k)
                if (lParam == ix[j].Button.Folder[k].Hwnd) ; Browse Folder
                    ix[j].EventHandler(5, k)
            }
        }
    }
    if (msg == WM_SYSCOMMAND && wParam == SC_CLOSE) {
        Loop, % ix.MaxIndex()
            if (hwnd == ix[A_Index].Hwnd)
                ix[A_Index].EventHandler(2)
    }
}

Can anyone offer any suggestions on how to fix the "tip"?

 

Also, I was not able to get the "actions" to work if I specified a function, but I could make them work using labels.  Instead, I just opted for parsing the ErrorLevel to sending it to the function directly.  I welcome any thoughts on getting that working, too.

 

Mike V.



MikeV
  • Members
  • 19 posts
  • Last active: Sep 13 2015 09:08 PM
  • Joined: 03 Jun 2013

After much more experimentation with this inputbox, I have two additional requests...

 

1. Add an option to allow the ESC key to trigger the Cancel button.

2. Add an option to allow the ENTER key to trigger the OK button.

 

Anyone?  Am I here all by myself....  :(

 

Mike V.



Guest10
  • Members
  • 1216 posts
  • Last active: Oct 30 2015 05:12 PM
  • Joined: 27 Oct 2012

evangelized on http://ahkscript.org for resolution. ;)



Coco
  • Members
  • 697 posts
  • Last active: Oct 31 2015 07:26 PM
  • Joined: 27 Jul 2012

Apologies for the delay, been busy. Please see new updated version: http://ahkscript.org....php?f=6&t=4559