Page 1 of 2

GeekDude's Tips, Tricks, and Standalones

Posted: 21 Apr 2015, 11:42
by geek
GeekDude's Tips, Tricks, and Standalones

This is intended to be a useful reference for any AutoHotkey scriptwriter regardless of their experience. If you find any of the examples to be confusing please let me know so I can update them for clarity.


Table of Contents
  • String Manipulation
  • Objects
  • Logic
  • DllCalls
  • Writing Libraries
  • Windows
  • Threading
  • GUIs
  • Regular Expressions
  • Networking and Web
  • Files and Folders

String manipulation

Repeat a string.

Code: Select all

StrRepeat(String, Times)
{
	return StrReplace(Format("{:0" Times "}", 0), "0", String)
}
Pad a string to a given length.

Code: Select all

Var := "abc123"

; Format can be used to left-pad with zeros
MsgBox, % Format("{:010}", Var)

; SubStr can be used for prefix padding or postfix padding with any character.
; However, if the string is already larger than the size you want to pad to,
; the string will be truncated.
MsgBox, % SubStr("-=-=-=-=-=" Var, 1-10)
MsgBox, % SubStr(Var "=-=-=-=-=-", 1, 10)
Remove duplicate delimiters when compiling a list in a loop WITHOUT using an if statement.

Code: Select all

Loop, 9
	List .= ", " A_Index

; SubStr can be used to remove a single delimiter
MsgBox, % SubStr(List, 3)

; LTrim can be used to remove many delimiters
MsgBox, % LTrim(List, ", ")
Check if a string starts with another string.

Code: Select all

; Using InStr
if (InStr("Monkey Tacos", "Monkey") == 1)

; Using SubStr (if you know the length of the other string)
if (SubStr("Monkey Tacos", 1, 6) == "Monkey")

; Using Regular Expressions
if ("Monkey Tacos" ~= "^Monkey")

Objects

Use standard JSON format when defining your objects by placing the definition into a continuation section.

Code: Select all

; http://www.json.org/example.html
MyObject :=
( LTrim Join
{
	"glossary": {
		"title": "example glossary",
		"GlossDiv": {
			"title": "S",
			"GlossList": {
				"GlossEntry": {
					"ID": "SGML",
					"SortAs": "SGML",
					"GlossTerm": "Standard Generalized Markup Language",
					"Acronym": "SGML",
					"Abbrev": "ISO 8879:1986",
					"GlossDef": {
						"para": "A meta-markup language, used to create markup languages such as DocBook.",
						"GlossSeeAlso": ["GML", "XML"]
					},
					"GlossSee": "markup"
				}
			}
		}
	}
}
)
Retrieve the item count from an associative array. Note that AutoHotkey v1.1.29 makes this unnecessary.

Code: Select all

Count := NumGet(&Array, 4*A_PtrSize)

Logic

Do a toggle with only one line.

Code: Select all

Loop
	MsgBox, % Toggle := !Toggle

DllCalls

Lock your screen with a simple DllCall.

Code: Select all

DllCall("LockWorkStation")
Output to a console window. Notes:
  • This doesn't work when running from SciTE4AHK.
  • This is not the same as using the standard output, so output done in this way will not appear if you run your script from the command prompt.

Code: Select all

; By hand using FileAppend. FileOpen could also have been used.
DllCall("AllocConsole")
Loop, 10
	FileAppend, %A_Index%`n, CONOUT$


; As a function using FileOpen. FileAppend could also have been used.
Print(Text){
	static c := FileOpen("CONOUT$", ("rw", DllCall("AllocConsole")))
	
	; Reference __Handle to force AHK to flush the write buffer. Without it,
	; Without it, AHK will cache the write until later, such as when the
	; file is closed.
	c.Write(Text), c.__Handle
}
Run command line tools without having a command prompt pop up by attaching to a hidden command prompt.

Code: Select all

; Launch a command promt and attach to it
DetectHiddenWindows, On
Run, cmd,, Hide, PID
WinWait, ahk_pid %PID%
DllCall("AttachConsole", "UInt", PID)

; Run another process that would normally
; make a command prompt pop up
RunWait, %ComSpec% /c ping localhost > %A_Temp%\PingOutput.txt

; Close the hidden command prompt process
Process, Close, %PID%

; Look at the output
FileRead, Output, %A_Temp%\PingOutput.txt
MsgBox, %Output%

Writing Libraries

When writing a library that has code which must be run before any of your functions can be used, you can create an initialization function that runs itself automatically when the script starts. This eliminates any need for the library's users to put your code into their auto-execute section.

Code: Select all

Initialize()
{
	Static Dummy := Initialize()
	MsgBox, This init function has been called automatically
}
If your library defines hotkeys, it can break other scripts that include it in their auto-execute section. You can avoid this by wrapping your hotkey definitions in if False.

Code: Select all

if False
{
	x::MsgBox, You hit x
	y::MsgBox, You hit y
}

MsgBox, Auto-execution not interrupted

Windows

Change the default script template by modifying C:\Windows\ShellNew\Template.ahk

Code: Select all

Run, *RunAs notepad.exe C:\Windows\ShellNew\Template.ahk
Start your scripts on login by placing shortcuts (or the actual scripts) into the Startup folder.

Code: Select all

FileCreateShortcut, %A_ScriptFullPath%, %A_Startup%\%A_ScriptName%.lnk

Threading

:!: WARNING :!:

These tricks DO NOT add real multithreading to AutoHotkey. They are bound by the limitations of AutoHotkey's green thread implementation and do not work around those restrictions in any way. If you would like to use real multithreading with AutoHotkey look into AutoHotkey_H or multiprogramming (multiple programs/scripts interacting with eachother).

:!: WARNING :!:

Create a new thread context using SetTimer with a small negative period.

One example of where this is useful is if you have a message handler from OnMessage that you have to do a lot of processing in, but that the result of doesn't actually change what you return to the sender of the message. By using SetTimer to schedule a new thread context to be created, you can be responsive and return immediately, then do your processing afterward.

Code: Select all

; The trick on its own
SetTimer, Label, -0


; The trick in context
WM_LBUTTONDOWN(wParam, lParam, Msg, hWnd)
{
	; Return immediately then handle the click afterward
	SetTimer, HandleClick, -0
	return
}

HandleClick:
MsgBox, You clicked!
return

GUIs

Create a menu bar for your GUI using a well formatted object instead of a long list of menu commands.

Code: Select all

; Create a well-formatted object using one of the tricks
; from the objects section of this document
Menu :=
( LTrim Join Comments
[
	["&File", [
		["&New`tCtrl+N", "LabelNew"],
		["&Open`tCtrl+O", "LabelOpen"],
		["&Save`tCtrl+S", "LabelSave"],
		[],
		["E&xit`tCtrl+W", "GuiClose"]
	]], ["&Edit", [
		["Find`tCtrl+F", "LabelFind"],
		[],
		["Copy`tCtrl+C", "LabelCopy"],
		["Paste`tCtrl+V", "LabelPaste"]
	]], ["&Help", [
		["&About", Func("About").Bind(A_Now)]
	]]
]
)

MenuArray := CreateMenus(Menu)
Gui, Menu, % MenuArray[1]
Gui, Show, w640 h480
return

LabelNew:
LabelOpen:
LabelSave:
LabelFind:
LabelCopy:
LabelPaste:
return

GuiClose:
Gui, Destroy

; Release menu bar (Has to be done after Gui, Destroy)
for Index, MenuName in MenuArray
	Menu, %MenuName%, DeleteAll

ExitApp
return

About(Time)
{
	FormatTime, Time, %Time%
	MsgBox, This menu was created at %Time%
}

CreateMenus(Menu)
{
	static MenuName := 0
	Menus := ["Menu_" MenuName++]
	for each, Item in Menu
	{
		Ref := Item[2]
		if IsObject(Ref) && Ref._NewEnum()
		{
			SubMenus := CreateMenus(Ref)
			Menus.Push(SubMenus*), Ref := ":" SubMenus[1]
		}
		Menu, % Menus[1], Add, % Item[1], %Ref%
	}
	return Menus
}

Regular Expressions

Find, and optionally replace, all matches of regular expression efficiently using a custom enumerator.

Code: Select all

Haystack =
(
abc123|

abc456|

abc789|
)

for Match, Ctx in new RegExMatchAll(Haystack, "O)abc(\d+)")
{
	if (Match[1] == "456")
		Ctx.Replacement := "Replaced"
}

MsgBox, % Ctx.Haystack


class RegExMatchAll
{
	__New(ByRef Haystack, ByRef Needle)
	{
		this.Haystack := Haystack
		this.Needle := Needle
	}
	
	_NewEnum()
	{
		this.Pos := 0
		return this
	}
	
	Next(ByRef Match, ByRef Context)
	{
		if this.HasKey("Replacement")
		{
			Len := StrLen(IsObject(this.Match) ? this.Match.Value : this.Match)
			this.Haystack := SubStr(this.Haystack, 1, this.Pos-1)
			. this.Replacement
			. SubStr(this.Haystack, this.Pos + Len)
			this.Delete("Replacement")
		}
		Context := this
		this.Pos := RegExMatch(this.Haystack, this.Needle, Match, this.Pos+1)
		this.Match := Match
		return !!this.Pos
	}
}

Networking and Web

Download a file from the web and use its contents without having to save to a temporary file.

Code: Select all

Address := "https://example.com/"

; Send a request for the resource we want using an HTTP Request object
Request := ComObjCreate("WinHttp.WinHttpRequest.5.1")
Request.Open("GET", Address)
Request.Send()


; If you want to get text data:

MsgBox, % Request.responseText


; If you want to get binary data:

; Get the data pointer and size. The pointer will be valid
; only as long as a reference to Request.responseBody is kept.
pData := NumGet(ComObjValue(Request.responseBody)+8+A_PtrSize, "UInt")
Size := Request.responseBody.MaxIndex()+1

; Do something with the binary data
FileOpen("BinaryFile.png", "w").RawWrite(pData+0, Size)

Files and Folders

Read a file directly in an expression using FileOpen.

Code: Select all

MsgBox, % FileOpen("C:\Windows\System32\drivers\etc\hosts", "r").Read()
Overwrite a file in one step using FileOpen.

Code: Select all

/* Old method:
	FileDelete, FileName.txt
	FileAppend, New contents, FileName.txt
*/

FileOpen("FileName.txt", "w").Write("New contents")


Revision History

You can view old versions of this post from the GitHub link below.

https://gist.github.com/G33kDude/1601bd ... /revisions

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 12:06
by smorgasbord
MsgBox, % StrSplit(FileOpen("No of files in 1 line.txt", "r").Read() "`n", "`n").MaxIndex()-1

by geekdude

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 12:11
by vasili111
I am experimenting with thread creation. Why it is not working?

Code: Select all

b := 1
SetTimer, label1, -0

return

label1:
Loop
{
    b := b + 1
    ToolTip, %b%
}

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 12:15
by geek
smorgasbord wrote:MsgBox, % StrSplit(FileOpen("No of files in 1 line.txt", "r").Read() "`n", "`n").MaxIndex()-1
For reference, this gets the number of newlines in a file. It's more of an exercise in compact code than a practical example.
vasili111 wrote:I am experimenting with thread creation. Why it is not working?
Persistence, I assume. You'll need #Persistent at the top of your script
#Persistent Documentation wrote:If this directive is present anywhere in the script, that script will stay running after the auto-execute section (top part of the script) completes. This is useful in cases where a script contains timers and/or custom menu items but not hotkeys, hotstrings, or any use of OnMessage() or Gui.

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 12:21
by vasili111
Thanks!
Why this is not working? I am getting in tooltip 1 after ~1 sec tooltip disappears. It should count in tooltip, right?

Code: Select all

#Persistent

b := 1
SetTimer, label1, -0

Loop
{
    ToolTip, %b%
}

return

label1:
Loop
{
    b := b + 1
}

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 12:31
by geek
That's a nuance of how threads in AutoHotkey work. AHK is single threaded, so what we get a "pseudo threads". AHK usually makes these work more or less how you would expect, but in this case they aren't. Instead of switching back and forth between threads, AHK waits for the new thread to end before going back to the old thread. Because of that, this technique cannot be used in this way.

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 12:36
by vasili111
GeekDude
So as I understand it is impossible to run more than one thread in the same time. In any given time only one thread will be running. Am I right?

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 12:47
by HotKeyIt
You would need AutoHotkey.dll for real multi-threading.
Following requires AutoHotkey_H (AutoHotkey[Mini].dll is included in Resources so all you need is AutoHotkey.exe).

Code: Select all

AhkMini("Alias(b," getvar(b:=1) ")`nLoop`nb++") ; use AhkThread to load AutoHotkey.dll instead of AutoHotkeyMini.dll
Loop
    ToolTip, %b%
Esc::ExitApp

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 12:49
by geek
Additional real threads, that is. Hey HotKeyIt, how do I handle code errors from calling the COM interface on your dll? I'm using ahktextdll, then addScript and finally ahkFunction. How do I handle the error from passing malformed code into addScript, or if something throws an exception during runtime?

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 13:00
by HotKeyIt
addScript will return a pointer to first line of created script.
When script could not be added 0 will be returned and error from dll will be shown as in AutoHotkey.exe, e.g. MsgBox % thread.addScript("This code will error")
This will show error at runtime:

Code: Select all

(thread:=ComObjCreate("AutoHotkey.Script")).ahkdll()
MsgBox % thread.addScript("ie:=ComObjCreate(""InternetExplorer.Application"")`nie.nav()`nie.Quit()",2)

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 13:12
by vasili111
HotKeyIt
To be able to use AutoHotkey_H as default AutoHotkey I should replace my installed AutoHotkey.exe with AutoHotkey.exe from your link?
Can you please give me link to AutoHotkey_H documentation?

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 13:13
by tmplinshi
Thank you, I like this one :D
Count := NumGet(&Array, 4*A_PtrSize)

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 14:17
by HotKeyIt
vasili111 wrote:HotKeyIt
To be able to use AutoHotkey_H as default AutoHotkey I should replace my installed AutoHotkey.exe with AutoHotkey.exe from your link?
Can you please give me link to AutoHotkey_H documentation?
Yes, simply replace the AutoHotkey.exe.

The old documentation is here, but it is not updated anymore: http://hotkeyit.ahk4.net/files/AutoHotkey-txt.html
The new v2 documentation (still not complete) is here: http://hotkeyit.github.io/v2/docs/AutoHotkey.htm
Most of the v2 features are available in v1 too.

The docs are also included in download pack
- v1: https://github.com/HotKeyIt/ahkdll-v1-r ... master.zip
- v2: https://github.com/HotKeyIt/ahkdll-v2-r ... master.zip


Btw. in AutoHotkey_H there is a Count method: MsgBox % {1:1,2:2,test:3,ahk:4}.Count()

Re: GeekDude's tips and tricks

Posted: 21 Apr 2015, 15:25
by vasili111
GeekDude
HotKeyIt

Thank you!

Re: GeekDude's tips and tricks

Posted: 30 Aug 2015, 22:18
by Coldblackice
Cool thread, thanks!

Re: GeekDude's tips and tricks

Posted: 05 Mar 2017, 10:08
by lmstearn
Bump- (In the hope there are more to add). Thanks for the post! :)

Re: GeekDude's tips and tricks

Posted: 05 Mar 2017, 14:00
by SnowFlake
"GeekDude's tips and tricks" what a lovely title xD also great work!

Re: GeekDude's tips and tricks

Posted: 31 Jul 2017, 08:14
by geek
This post has been updated. You can track the revision history here on GitHub.

Re: GeekDude's tips and tricks

Posted: 12 Oct 2017, 10:41
by burque505
@GeekDude, thank you.
The tip on pseudo-threads is a life-saver! Instead of :headwall: in both LinqPad and UiPath, where I am experimenting with AutoHotkey.Interop, I can now avoid the following error:
Error in #include file "Whatever_file.ahk":
0x800101D - An outgoing call cannot be made since the application is dispatching an input-synchronous call
By just adding

Code: Select all

#Persistent
SetTimer, myLabel, -0
return
myLabel:
to the top of my code in my scripts that threw that error, and just putting my entire code below the label, I can now create COM objects without calling another script.

By the way,
I have tried Vasili111's code:

Code: Select all

#Persistent

b := 1
SetTimer, label1, -0

Loop
{
    ToolTip, %b%
}

return
in both AHK_H and AHK_L, and it works verbatim for me. Tooltip keeps counting until I shut down the script from the tray. Win7 x64

:bravo:

Thank you!

Re: GeekDude's tips and tricks

Posted: 23 Oct 2017, 10:59
by derz00
@GeekDude :wave:

Why doesn't this work?

Code: Select all

MyFunction()
{
    Static Dummy := MyFunction()
    MsgBox, I see you %A_UserName%
	wow:="oops " A_Now
	return wow
}
MsgBox % Dummy
I'm just misunderstanding something, but I just can't see what. The second MsgBox is blank.

Any idea why this doesn't work in AHK v2?

Code: Select all

MyObject := " 
( LTrim Join
{
	math: "fun",
	english: "boring",
 	coding: "exciting"
}
)"
; when the continuation section isn't wrapped in quotes, v2 throws an error
; When it does have quotes, the array is blank
for key, nme in MyObject
	MsgBox % key " " nme