GeekDude's Tips, Tricks, and Standalones

Put simple Tips and Tricks that are not entire Tutorials in this forum
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

GeekDude's Tips, Tricks, and Standalones

21 Apr 2015, 11:42

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
Last edited by geek on 30 Aug 2015, 22:19, edited 10 times in total.
User avatar
smorgasbord
Posts: 493
Joined: 30 Sep 2013, 09:34

Re: GeekDude's tips and tricks

21 Apr 2015, 12:06

MsgBox, % StrSplit(FileOpen("No of files in 1 line.txt", "r").Read() "`n", "`n").MaxIndex()-1

by geekdude
John ... you working ?
vasili111
Posts: 747
Joined: 21 Jan 2014, 02:04
Location: Georgia

Re: GeekDude's tips and tricks

21 Apr 2015, 12:11

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%
}
DRAKON-AutoHotkey: Visual programming for AutoHotkey.
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: GeekDude's tips and tricks

21 Apr 2015, 12:15

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.
vasili111
Posts: 747
Joined: 21 Jan 2014, 02:04
Location: Georgia

Re: GeekDude's tips and tricks

21 Apr 2015, 12:21

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
}
DRAKON-AutoHotkey: Visual programming for AutoHotkey.
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: GeekDude's tips and tricks

21 Apr 2015, 12:31

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.
vasili111
Posts: 747
Joined: 21 Jan 2014, 02:04
Location: Georgia

Re: GeekDude's tips and tricks

21 Apr 2015, 12:36

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?
DRAKON-AutoHotkey: Visual programming for AutoHotkey.
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

Re: GeekDude's tips and tricks

21 Apr 2015, 12:47

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
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: GeekDude's tips and tricks

21 Apr 2015, 12:49

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?
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

Re: GeekDude's tips and tricks

21 Apr 2015, 13:00

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)
vasili111
Posts: 747
Joined: 21 Jan 2014, 02:04
Location: Georgia

Re: GeekDude's tips and tricks

21 Apr 2015, 13:12

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?
Last edited by vasili111 on 21 Apr 2015, 13:14, edited 1 time in total.
DRAKON-AutoHotkey: Visual programming for AutoHotkey.
tmplinshi
Posts: 1604
Joined: 01 Oct 2013, 14:57

Re: GeekDude's tips and tricks

21 Apr 2015, 13:13

Thank you, I like this one :D
Count := NumGet(&Array, 4*A_PtrSize)
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

Re: GeekDude's tips and tricks

21 Apr 2015, 14:17

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()
vasili111
Posts: 747
Joined: 21 Jan 2014, 02:04
Location: Georgia

Re: GeekDude's tips and tricks

21 Apr 2015, 15:25

GeekDude
HotKeyIt

Thank you!
DRAKON-AutoHotkey: Visual programming for AutoHotkey.
User avatar
Coldblackice
Posts: 29
Joined: 13 Nov 2013, 14:39

Re: GeekDude's tips and tricks

30 Aug 2015, 22:18

Cool thread, thanks!
User avatar
lmstearn
Posts: 688
Joined: 11 Aug 2016, 02:32
Contact:

Re: GeekDude's tips and tricks

05 Mar 2017, 10:08

Bump- (In the hope there are more to add). Thanks for the post! :)
:arrow: itros "ylbbub eht tuO kaerB" a ni kcuts m'I pleH
User avatar
SnowFlake
Posts: 368
Joined: 28 Apr 2015, 05:41
Contact:

Re: GeekDude's tips and tricks

05 Mar 2017, 14:00

"GeekDude's tips and tricks" what a lovely title xD also great work!
:yawn:
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: GeekDude's tips and tricks

31 Jul 2017, 08:14

This post has been updated. You can track the revision history here on GitHub.
burque505
Posts: 1731
Joined: 22 Jan 2017, 19:37

Re: GeekDude's tips and tricks

12 Oct 2017, 10:41

@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!
User avatar
derz00
Posts: 497
Joined: 02 Feb 2016, 17:54
Location: Middle of the round cube
Contact:

Re: GeekDude's tips and tricks

23 Oct 2017, 10:59

@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
try it and see
...

Return to “Tips and Tricks (v1)”

Who is online

Users browsing this forum: No registered users and 17 guests