 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Did you find this script useful? |
| Yes |
|
100% |
[ 6 ] |
| No |
|
0% |
[ 0 ] |
|
| Total Votes : 6 |
|
| Author |
Message |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Wed May 09, 2007 12:17 pm Post subject: Automate repetitive tasks in Windows (especially Excel) |
|
|
This script has saved me hours of really boring work, so I thought I should share it with you guys.
This script looks for a repetitive pattern in your keystrokes and mouse clicks. When it is sure that it has discovered a repetitive pattern, it asks you whether you would like to repeat the pattern for example 100 times.
The script is especially useful in Excel where I keep ending up in the situation where I have to make a small modification to 100 cells (like adding a $ somewhere across a range of formulas). With this script I make the modification to three cells so the script is sure there is a pattern. Then I just sit back and watch the magic.
The script is also supposed to recognize a pattern in your mouse clicks. This is notoriously difficult, because the script cannot know whether the user meant to do the same action even though he clicked 10 pixels to the right compared to last time. I have tried to solve this by creating a function where the script takes the freedom to force your mouse to the last position if it suspects that you are doing something repetitive and come close to the position you clicked at the last time. Get it? This part is although at best very buggy so please help me with this part.
How to test the script:
- Start the script
- Start typing this exact text (do not use copy and paste):
egg egg egg egg egg egg egg egg egg
At some point the script will discover the pattern and a tooltip will arise asking you to hold down the Windows Button (to the right of your left control button) and press V. Then it will ask you how many times you would like to repeat the task (as shown on the screenshot).
In the future the following functionality should be included in the script:
- It should recognize Monday, Tuesday, Wednesday as a pattern and automate the typing of the rest of the values. This goes for the months of the year, the alphabet, numbers (even 2, 4, 6). These patterns should be saved in INI files. I took the liberty of making a wiki for it at http://www.autohotkey.com/wiki/index.php?title=Repeater_Patterns so you can add more.
- The script should recognize that the computer is working using the code below so it will not run faster than the computer can handle.
- The script should record the title of the window you are working in and stop if the window title is not the same as when the pattern was recorded
- The user should be able to save the patters and recall them using a user-defined hotkey.
I hope you find this helpful. Please help make this script into the Rolls Royce of scripts.
Code of the script:
| Code: |
thread = 0
checkBackTo = 2
patternStartsAt = -1
lengthOfPattern = -1
patternFound = false
startOn = -1
Array = -1
ArrayLength = 0
;MouseX0 = -1
;SetKeyDelay, 400 ;sets the speed of the playback
ADOPTLANGUAGE() ;This method changes the keyboard layout of the script to equal the one of the active window. I do not know if this is necessary anymore.
;this loop runs each time a key is pressed (it stops and waits for a keypress at the line, which starts with "Input, key"
Loop
{
ArrayLength += 1
Input, Key, MVL1, {Enter}{Escape}{F1}{F2}{F3}{F4}{F5}{F6}{F7}{F8}{F9}{F10}{F11}{F12}{Left}{Right}{Up}{Down}{Home}{End}{PgUp}{PgDn}{Del}{Ins}{BS}{Capslock}{Numlock}{PrintScreen}{Pause}
;if one of the "end" characters is pressed then errorlevel = "max" (or is it <> "max" i am not sure). An end character is one of the following {Enter}{Escape}{F1}{F2}{F3}{F4}{F5}{F6}{F7}{F8}{F9}{F10}{F11}{F12}{Left}{Right}{Up}{Down}{Home}{End}{PgUp}{PgDn}{Del}{Ins}{BS}{Capslock}{Numlock}{PrintScreen}{Pause}
if ErrorLevel <> Max
{
StringTrimLeft, Key, ErrorLevel, 7
Key = {%Key%}
ifEqual, key, {Backspace}
StringTrimRight, token, token, 1
else
token =
}
else
ifEqual, thread, 0
{
if (key = A_Space or key = A_Tab)
token =
else
token = %token%%key%
}
if (Key = "{" or Key = "}" or Key = "^" or Key = "+" or Key = "#" or Key = "!")
Key = {%Key%}
StringReplace, key, key, % chr(3), c, All ; because control + c is recognized as a special character.
StringReplace, key, key, % chr(22), v, All ; because control + v is recognized as a special character.
Key := INCLUDESTATE(Key, "Shift")
Key := INCLUDESTATE(Key, "Control")
Key := INCLUDESTATE(Key, "Alt")
Key := INCLUDESTATE(Key, "LWin")
;save the key which was pressed to the array containing all the keys
saveKey(key)
}
return
;when a pattern has been recognized, the user is free to press this hotkey to "play back" the pattern
#v::
{
thread = 1
WinGetActiveTitle, title
ifEqual, patternFound, true
{
InputBox, repititions, Repeat?, How many times would you like to repeat the task?, , 230, 200
ifEqual, ErrorLevel, 0
{
WinActivate, %title%
PLAYBACK(repititions)
ArrayLength = 0
}
}
return
}
;this is just to reload the script
~^s::
{
sleep, 500
reload
return
}
;addes a new key to the array containing all the keys.
saveKey(key)
{
global
ifEqual, thread, 0
{
fits := CHECKPATTERN(ArrayLength, Array, Key)
; ToolTip, %fits%, 140, 140, 2
Array%ArrayLength% := Key
; ToolTip, ArrayLength %ArrayLength% key: %key%, 100, 100
if fits <> -1
{
patternFound = true
startOn = %fits%
ShowMessage()
}
else
{
patternFound = false
}
}
thread = 0
}
; this method is executed when a pattern has been recognized and the user has decided how many repetitions to make.
; this method then does the actual repetitions.
PLAYBACK(repititions)
{
global lengthOfPattern, patternStartsAt, startOn, arrayLength
patternEndsAt := patternStartsAt + lengthOfPattern - 1
startOnIndex = %startOn%
;ToolTip, startOn: %startOn%, 100, 150, 4
if (arrayLength > 3)
{
loop
{
line := Array%startOnIndex%
Send %line%
;MsgBox, %startOnIndex% = %patternEndsAt%
ifEqual, startOnIndex , %patternEndsAt%
break
startOnIndex += 1
}
Loop %repititions%
{
index = %patternStartsAt%
Loop
{
line := Array%index%
Send %line%
ifEqual, index, %patternEndsAt%
break
index += 1
}
}
startOn = %patternStartsAt%
}
reload
return
}
;this method checks whether there is a pattern in the array containing all the keys (this array is named "array").
;This method sets the value of patternStartsAt and lengthOfPattern.
;It returns -1 if no pattern is recognized
CHECKPATTERN(ArrayIndex, Array, Key)
{
ArrayLength = %ArrayIndex%
ArrayIndex -= 1
equalsUntilIndex := EQUALCHARS(Array, ArrayLength, Key)
;ToolTip, equalsUntilIndex ArrayIndex: %equalsUntilIndex% and %ArrayIndex%, 100, 180, 1
if (equalsUntilIndex < ArrayIndex)
ArrayIndex = %equalsUntilIndex%
Loop
{
line := Array%ArrayIndex%
;ToolTip, line key: %line% %key%, 100, 180, 2
ifEqual, line, %Key%
{
extraChars = 2 ;to be really sure that there is a pattern
distance := ArrayLength - ArrayIndex + extraChars
checkFirst := ArrayIndex - 1
checkLast := ArrayLength - 1
loop
{
if (checkLast < 0)
break
firstChar := Array%checkFirst%
lastChar := Array%checkLast%
if (firstChar <> lastChar)
break
; ToolTip, firstChar: %firstChar%, 100, 310, 8
ifEqual, firstChar, {Mouse}
{
; ToolTip, % " mouseX%checkFirst%: " mouseX%checkFirst% " mouseY%checkFirst%: " mouseY%checkFirst% " `n mouseX%checkLast%: " mouseX%checkLast% " mouseY%checkLast%: " mouseY%checkLast%, 100, 180, 5
if (mouseX%checkFirst% <> mouseX%checkLast%) or (mouseY%checkFirst% <> mouseY%checkLast%) or (mouseButton%checkFirst% <> mouseButton%checkLast%)
{
FixMouse(mouseX%checkLast%, mouseY%checkLast%)
return -1
}
else
ToolTip, egg, 100, 280, 6
}
distanceChecked := ArrayIndex - checkFirst
ifEqual, distance, %distanceChecked%
{
global patternStartsAt, lengthOfPattern
patternStartsAt := arrayIndex - distance
lengthOfPattern := distance - extraChars
arrayIndex := arrayIndex - distance + extraChars + 1
ifEqual, lengthOfPattern, 2
arrayIndex := arrayIndex - extraChars
ifEqual, lengthOfPattern, 3
arrayIndex := arrayIndex - extraChars - 1
return %arrayIndex%
}
checkFirst -= 1
checkLast -= 1
}
}
;maximum amount of keys that are taken into consideration
maxKeys = 30
checkedSoFar := ArrayLength - ArrayIndex
if (ArrayIndex < 2 OR checkedSoFar > maxKeys)
break
ArrayIndex -= 1
}
return -1
}
;This method makes the mouse "gravitate" towards the position of lastX and lastY if it comes closer than the value of "diff".
FixMouse(lastX, lastY)
{
; ToolTip, %lastX% and %lastY%, 100, 280, 6
unEnable = false
diff = 30 ;the mouse is gravitated if it comes closer than this amount of pixels.
unEnableDiff = 60 ;this variable indicates how far the user has to move the mouse away from the "center of gravity" before the script gives up and lets the user move his mouse freely. This is indicated by setting unEnable to true
touched = false
;MouseMove, %lastX%, %lastY%
loop
{
MouseGetPos, xPos, yPos
ifEqual, touched, true
if (xPos > lastX + unEnableDiff) or (xPos < lastX - unEnableDiff) or (yPos > lastY + unEnableDiff) or (yPos < lastY - unEnableDiff)
unEnable = true
if (xPos < lastX + diff) and (xPos > lastX - diff) and (yPos < lastY + diff) and (yPos > lastY - diff)
ifNotEqual, unEnable, true
{
MouseMove, %lastX%, %lastY%
touched = true
}
sleep, 20
}
}
;this method checks whether the last characters in "array" are equal. If the user types dddddddddddddddddddddddddddddddddddd then the repeater does not need to pop up to ask whether the user wants to repeat the pattern "d".
EQUALCHARS(array, arrayLength, key)
{
index := ArrayLength - 1
loop
{
if (key <> array%index%)
return %index%
ifEqual, index, 0
return %index%
index -= 1
}
}
;this method adds {control down}d{control up} to if the user pressed control+d.
INCLUDESTATE(Key, modifier)
{
GetKeyState, state, %modifier%
if (state = "d")
{
Key = {%modifier% down}%key%{%modifier% up}
}
return key
}
;This method changes the keyboard layout of the script to equal the one of the active window. I do not know if this is necessary anymore.
ADOPTLANGUAGE()
{
SetFormat, integer, hex
WinGet, hw_notepad, ID, Repeater Test.ahk ahk_class AutoHotkey
tid_notepad := DllCall( "GetWindowThreadProcessId", "uint", hw_notepad, "uint", 0 )
lang_id := DllCall( "GetKeyboardLayout", "uint", tid_notepad )
WinGet, hw_notepad, ID, A
tid_notepad := DllCall( "GetWindowThreadProcessId", "uint", hw_notepad, "uint", 0 )
lang_id := DllCall( "GetKeyboardLayout", "uint", tid_notepad )
DetectHiddenWindows On
SetTitleMatchMode 2
WinGet, this_id, ID, Repeater Test.ahk ahk_class AutoHotkey
PostMessage, 0x0050, 0, %lang_id%, , ahk_id %this_id%
WinGet, hw_notepad, ID, Repeater Test.ahk ahk_class AutoHotkey
tid_notepad := DllCall( "GetWindowThreadProcessId", "uint", hw_notepad, "uint", 0 )
lang_id := DllCall( "GetKeyboardLayout", "uint", tid_notepad )
SetFormat, integer, D
return
}
ShowMessage()
{
ToolTip, Press the Windows key + V to repeat your last keyboard actions, 1, 1
SetTimer, RemoveTrayTip, 4000
return
RemoveTrayTip:
SetTimer, RemoveTrayTip, Off
ToolTip, , 1, 1
return
}
~LButton:: mouseClicked("LButton")
~RButton:: mouseClicked("RButton")
~MButton:: mouseClicked("MButton")
; This method is called every time the mouse button is clicked.
MouseClicked(button)
{
global
MouseGetPos, xPos, yPos
button = %button%
mouseButton%ArrayLength% = %button%
mouseX%ArrayLength% = %xPos%
mouseY%ArrayLength% = %yPos%
saveKey("{Mouse}")
ArrayLength += 1
}
|
Code to recognize that the computer is working (determines whether the mouse cursor is an hourglass (I got this code from some of you wonderful people on this forum, please fill me in on who it was). It would be good if the script would also recognize how many percent of CPU capacity and harddisk read and write capacity is being used so it can wait for a while if the computer is busy:
| Code: | IsCursorHourGlass()
{
cursor_id = 32512|32513|32514|32515|32516|32640|32641|32642|32643|32644|32645|32646|32648|32649|32650|32651
cursor_id32512?name = ARROW
cursor_id32513?name = IBEAM
cursor_id32514?name = WAIT
cursor_id32515?name = CROSS
cursor_id32516?name = UPARROW
cursor_id32640?name = SIZE
cursor_id32641?name = ICON
cursor_id32642?name = SIZENWSE
cursor_id32643?name = SIZENESW
cursor_id32644?name = SIZEWE
cursor_id32645?name = SIZENS
cursor_id32646?name = SIZEALL
cursor_id32648?name = NO
cursor_id32649?name = HAND
cursor_id32650?name = APPSTARTING
cursor_id32651?name = HELP
loop, parse, cursor_id, |
{
h_cursor := DllCall( "LoadCursor", "uint", 0, "uint", A_LoopField )
if ( !ErrorLevel and h_cursor )
h_cursor%h_cursor%?name := cursor_id%A_LoopField%?name
}
VarSetCapacity( ci, 20, 0 )
ci := Chr( 20 )
success := DllCall( "GetCursorInfo", "uint", &ci )
h_cursor := *( &ci+8 )+( *( &ci+9 ) << 8 )+( *( &ci+10 ) << 16 )+( *( &ci+11 ) << 24 )
if h_cursor%h_cursor%?name=
cursor = UNKNOWN
else
cursor := h_cursor%h_cursor%?name
if (cursor = "APPSTARTING" or cursor = "WAIT")
return, 1
else
return, 0
} |
Last edited by David Andersen on Wed May 09, 2007 6:10 pm; edited 2 times in total |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 3593 Location: Belgrade
|
Posted: Wed May 09, 2007 12:54 pm Post subject: |
|
|
Nice, I was thinking to create the same thing but for universal usage. _________________
 |
|
| Back to top |
|
 |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Wed May 09, 2007 12:57 pm Post subject: |
|
|
| Good to hear that you like it. It is actually for universal usage. You can for example copy and paste lots of data between applications by using ALT+TAB to switch between the windows. |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 3593 Location: Belgrade
|
Posted: Wed May 09, 2007 1:02 pm Post subject: |
|
|
No need for that, I user more serious tool to handle clipboard (CLCL).
But some for of repetitive keyboard operation, is present everywhere from time to time. _________________
 |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 3593 Location: Belgrade
|
Posted: Wed May 09, 2007 1:04 pm Post subject: |
|
|
I just tested it. It is amazing. Much better then what I have planned. In my visions I had to record keystorkes to be repeated, but automatic handling is much much beter.
Superb script David. Thx for creating it. _________________
 |
|
| Back to top |
|
 |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Wed May 09, 2007 2:06 pm Post subject: |
|
|
| I am really happy you found it useful. I really hope we can make it better so also the ability to use the mouse will work. |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 3593 Location: Belgrade
|
Posted: Wed May 09, 2007 3:10 pm Post subject: |
|
|
Well, for mouse you can't catch patern, so it will be very hard. Also, every pixel counts, so if you move a win just a little it will not be good. Contrary to that, you can do whatever you like when keyboard is in question.
I will think about this more. Some less flexible solution can certanly be made. _________________
 |
|
| Back to top |
|
 |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Wed May 09, 2007 3:15 pm Post subject: |
|
|
Hi majkinetor
To see how the intended solution will work do the following while the script is running:
type "d", then click the mouse, type "d" then click the mouse at the exact same spot, repeat this a couple of times. Then try to move your mouse slightly, notice that the mouse gets sucked to the exact position you have been clicking. If you force it away, the script will "give up" and you can move your mouse freely. In this way the the script can be sure that you really wanted to click at the same spot. |
|
| Back to top |
|
 |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Wed May 09, 2007 3:29 pm Post subject: |
|
|
I have added a small fix for control + c and control + v to function more stabile:
| Code: | StringReplace, key, key, % chr(3), c, All ; because control + c is recognized as a special character.
StringReplace, key, key, % chr(22), v, All ; because control + v is recognized as a special character. |
|
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 3593 Location: Belgrade
|
Posted: Wed May 09, 2007 3:36 pm Post subject: |
|
|
I have one suggestion. Can you add hotkeys to configuration at the top of the script ?
I tried d mouse d mouse and I must say I am not happy about that solution. Its hardly possible not to move the mouse. This should work without influencing current mouse position, just by simulating clicks.
Well, it would have to remember mouse coordinates based on the active window, so you can move window around and still have the ability to automate.
The bottom line: Mouse (double)clicks and realtive coordinates are saved, and keyboard presses. When you repeat, you do by sending mouse clicks on those positions. You should have configuration option for mouse treshold. Lets say I want to select one word, double it then do that with next word. The pattern si this:
ctrl shift right, click B, ctrl right arrow
Now if you repeat this patern 10 times, you will have 10 words bold.
You see that "click B" part is very hard to record, as B is button and this patern is hard to reproduce without suptile errors. This can be fixed by two things.
1. Add option to specify pattern manuely, or to edit the recorded patern
2. Add treshold to mouse position. Lets say B is clicked in the middle of the button first time. Second time, slightly bellow middle, third time the button is clicked but in upper left corner. If you set treshold to clicks, lets say 20px, it will catch every click as it is button center click.
3. Perhaps, upon click, yoru script should check what control is clicked and postion. Then if control is the same and position is similar you can add control to the pattern:
ctrl shift right, Button24 X14 Y20, ctrl right arrow
Fine suggestion would be to save patern for future reference. There are many places where the same patterns are repeated constantly.
Well, I hope you don't mind ideas. _________________
 |
|
| Back to top |
|
 |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Wed May 09, 2007 4:16 pm Post subject: |
|
|
Thanks a lot for your ideas! As I understood from the beginning that the task of making this script into a Rolls Royce is huge I would really appreciate your help with implementing these ideas.
I understand your suggested mouse functionality and I love the idea of recording the control you were clicking on (this would work in some cases, although not well in Excel), but I believe I have a better suggestion:
In your example: ctrl shift right, click B, ctrl right arrow (i guess "click B" means that you use the mouse to click on the bold icon i.e. in Word) the script would record:
ctrl shift right, left mouse 240, 200, ctrl right arrow
if you the second time click
ctrl shift right, left mouse 232, 212, ctrl right arrow
Then the script will have a clue that you are doing something repetitive because the mouse clicks are relatively near. The third time, the script will use its function to "suck" the mouse towards the 232, 212 position, if you resist, it will give up, but if you don't it will know that you are doing something repetitive.
It is a good suggestion to make the mouse clicks relative to the position of the window.
It would be great if we could implement the ability to manually specify the pattern, this could be combined with the function to save the patterns. |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 3593 Location: Belgrade
|
Posted: Wed May 09, 2007 5:14 pm Post subject: |
|
|
| Quote: | | It would be great if we could implement the ability to manually specify the pattern, this could be combined with the function to save the patterns. |
This would be great indeed. Then we can name common paterns and put them in the combo. For instance:
1. Make each next word bold (unversal)
2. Add string to each excell cell in the row
3. ....
I can create some fancy GUI for this, that will pop up with presets and little edit above to enter new pattern quickly.
I will have to examine your script throughly though. Perhaps you can speed this up by explaning me current codeflow in english or short pseudocode. I like your coding style anyway, so it isn't much problem to adopt your code. _________________
 |
|
| Back to top |
|
 |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Wed May 09, 2007 6:15 pm Post subject: |
|
|
That's a good idea have a list of the saved patterns.
I have cleaned up the code a bit and added quite a few comments to make it easier for you to understand the code. Basically it can be described as this:
loop
{
save_all_keypresses(key)
}
save_all_keypresses(key)
{
checkIfThereIsAPattern(array_of_keys)
}
checkIfThereIsAPattern(array_of_keys)
{
if(thereIsAPatternInTheKeys) askUserIfHeWantsAPlayBack()
}
playBackPattern(array_of_keys, startOfPattern, lengthOfPattern)
{
send, {key}{key}{mouseclick}
} |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 3593 Location: Belgrade
|
Posted: Wed May 09, 2007 6:40 pm Post subject: |
|
|
Thx. _________________
 |
|
| Back to top |
|
 |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Thu May 10, 2007 4:19 pm Post subject: |
|
|
Another idea for future development would be that the script uses UrlDownload on the Wiki page containing all the patterns (like January, February, etc): http://www.autohotkey.com/wiki/index.php?title=Repeater_Patterns
The script should download a fresh version each time it starts up.
This function would require rendering of the Wikipage converting it into an INI file (but this would be great functionality anyway for other scripts which could benefit from collaborative editing).
After a while there could be an extensive ini/wiki containing all imaginable patterns. Ok, maybe I am naive, but it would certainly be great! |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|