Question
Basically, the title says it all! How do I stop execution of my hotkey via a hotkey?
How do I do so without closing the script?
How do I explicitly set a hotkey to take priority over a specific loop?
Explanation
I have a loop that executes several hundred times (code attached), and I would like to create a kill switch. I don't want to have to re-open the script if I stop it running, and I need to have lengthy sleeps, so I can fool with the mouse, as it is.
I'm looking at various pages, such as tutorial and break.
What I've tried
For example, should I name my loop somehow? Because Break takes no parameters, I'm thinking: there's no way to specify what to break.
Another example: should I add a hotkey into my loop to break? If so, how do I do that? Just throw ^!p:: into a nest under each hotkey? Or is there another way to add a hotkey to a hotkey (for example, with GetKeyState)?
Answer format
If possible, please show don't tell your answers, using the following code:
^!i::
{
Loop 1300
{
Send ^+{PrintScreen}
Sleep 1500
MouseClickDrag, left, 0, 100, 600, 400
Sleep 1000
Send 1
Sleep 500
}
Return
}
^!o::
{
Loop 1323
{
Send ^+{PrintScreen}
Sleep 1500
MouseClickDrag, left, 0, 200, 600, 400
Sleep 1000
Send 1
}
Return
}
If you want a controlled stop, i.e. at a certain point in the script, then you could use a hotkey to set a variable and test that variable in the script. When the variable has been set and the IF statement is true, you reset that variable and exit the loop. Otherwise, you could kill the script at a random point through the reload command. This way the script is closed, but immediately reopened.
^!i::
MyStopVariable:=0 ; Set the variable to 0
Loop 1300
{
If MyStopVariable
Exit ; Or Break if you want to finish something of after the loop
Send ^+{PrintScreen}
Sleep 1500
MouseClickDrag, left, 0, 200, 600, 400
Sleep 1000
If MyStopVariable
Exit ; Or Break if you want to finish something of after the loop
Sleep 500
}
Return
^!o::
MyStopVariable:=0 ; Set the variable to 0
Loop 1323
{
If MyStopVariable
Exit ; Or Break if you want to finish something of after the loop
Send ^+{PrintScreen}
Sleep 1500
MouseClickDrag, left, 0, 200, 600, 400
Sleep 1000
If MyStopVariable
Exit ; Or Break if you want to finish something of after the loop
Send 1
}
Return
^!s:: ; Ctrl+Alt+s = stop
MyStopVariable:=1
Return
Alternatively, you could use your original code and add a reload.
^!s:: ; Ctrl+Alt+s = stop
Reload
Return
Related
I'm having a little problem with my script here using a Logitech mouse. I will be using it for farming in a game.
function OnEvent(event, arg)
if event == "MOUSE_BUTTON_PRESSED" and arg == 5 then
for i = 0, 300 do
PressAndReleaseKey("f9")
Sleep(400)
PressAndReleaseKey("enter")
Sleep(600)
PressAndReleaseKey("f5")
Sleep(50)
PressMouseButton(1)
Sleep(50)
ReleaseMouseButton(1)
end
PressAndReleaseKey("1")
repeat
until IsMouseButtonPressed(3)
end
end
So it will loop for 300 times and then press 1 when it's done, then repeat the loop again for 300 times, so on & so on. Problem I'm facing is, when I'm trying to abort the script, it will first finish the for-loop before being stopped by using Right-click button(IsMouseButtonPressed(3)), which is really hard to time (300x is a lot)
How can I pause/stop it during the for-loop, would it be possible?
Frequently check if the button is pressed and break the loop.
Break up those long blocking Sleeps.
Instead of Sleep(400) consider doing something like
for i = 1, 400, 50 do
Sleep(50)
if IsMouseButtonPressed(3) then break end
end
You can use break to jump out of your for loop when IsMouseButtonPressed(3)
for i = 0, 300 do
if IsMouseButtonPressed(3) then
break -- exit for loop.
end
PressAndReleaseKey("f9")
Sleep(400)
PressAndReleaseKey("enter")
Sleep(600)
PressAndReleaseKey("f5")
Sleep(50)
PressMouseButton(1)
Sleep(50)
ReleaseMouseButton(1)
end
doing it like this means you can expect a maximum delay of 1.1 seconds due to the sleep calls, for the exit to be registered.
You could change your code by adding a function to poll IsMouseButtonPressed(3) during your sleep intervals.
local function MousePollingSleep(time)
loopCount = time / 50
for i = loopCount, 0, -1 do
if IsMouseButtonPressed(3) then
return false
end
sleepTime = (i >= 1 and 1 or i) * 50
Sleep(sleepTime)
end
return true
end
and change your for loop to
function OnEvent(event, arg)
if event == "MOUSE_BUTTON_PRESSED" and arg == 5 then
repeat
for i = 0, 300 do
PressAndReleaseKey("f9")
if MousePollingSleep(400) == false then break end
PressAndReleaseKey("enter")
if MousePollingSleep(600) == false then break end
PressAndReleaseKey("f5")
if MousePollingSleep(50) == false then break end
PressMouseButton(1)
if MousePollingSleep(50) == false then break end
ReleaseMouseButton(1)
end
ReleaseMouseButton(1)
PressAndReleaseKey("1")
until IsMouseButtonPressed(3)
end
end
Alright so the question is hard to word. I've googled this several times, but usually it turns out I'm not googling the right phrases, and the answer is readily available. I do work as a medical biller for a doctors office, and I have to include diagnosis codes for the office visits. In the Electronic Medical Record program, there's a diagnosis list. The first line is the doctors description, I don't care about that. The second line is an ICD-9 code. Those are old, I don't care about those either. The third line (every third line) contains the ICD-10 code. That's what I need. What I'd like to be able to do is grab the whole list, dump it into an array delimited by new lines, and get rid of every element that doesn't contain a specific string. Then, dump all the kept elements into another array (or the same one, but not separated by 3 like they would be after the removals) and remove that prefixed string that I kept elements based on. After that, I need to click a specific spot (I know how to do this), add exactly four of the arrays elements as text (can't figure this out), hit enter, and keep adding and hitting enter until I've entered all of the array. I will post what I've tried cobble together from google searches if anyone wants to see that mess. But a general explanation on how to do this would also be appreciated. Thanks.
first of all, the stuff I'd be copying would look like this (actual example)
Lumbar stenosis - Primary
ICD-9-CM: 724.02
ICD-10-CM: M48.06
Spondylolisthesis of lumbar region
ICD-9-CM: 738.4
ICD-10-CM: M43.16
Lumbar degenerative disc disease
ICD-9-CM: 722.52
ICD-10-CM: M51.36
Chronic bilateral low back pain with bilateral sciatica
ICD-9-CM: 724.2, 724.3, 338.29
ICD-10-CM: M54.42, M54.41, G89.29
Naturally the list would be much longer.
The string I'd look for to keep the lines would be "ICD-10-CM: ", just so you guys know. I DID try using it as a delimiter, in quotes, but got some really quite weird results. It would have made this problem slightly easier to solve had that worked as the delimiter.
Arrays:={}
RealArray:={}
^j::
sendinput, ^c
sleep 20
bigone:=ClipBoard
sleep 2000
;StringReplace, bigone, newbigone, `n, "DLMTR", All
;Arrays:= StrSplit(newbigone, "DLMTR")
StringSplit, Arrays, bigone, `n
k=4
j=1
loop
{
if (k<Arrays.Max_Index)
{
RealArray%j%=Arrays%k%
j++
k++
k++
k++
}
else
return
}
return
^L::
a=0
loop
{
if (a<RealArray.Max_Index)
{
send RealArray%a%
a++
sendinput, {Space}
if(mod(a,5)==0)
sendinput, {enter}
}
else
return
}
Program
^j collects codes containing "ICD-10", ^k pastes the codes formatted 5 per line
^j::copyit()
^l::pasteit()
copyit()
{
sendinput, ^c
sleep 20
bigone := ClipBoard
sleep 100
global matches
matches := []
numMatches := 0
Loop parse, bigone, `n
Loop parse, A_LoopField, `,
if InStr(A_LoopField, "ICD-10")
matches[++numMatches] := trim( substr(A_LoopField, InStr(A_LoopField, ":") + 1), " `t`n`r")
}
pasteit()
{
global matches
for index, element in matches
{
Send %element%{SPACE}
if mod(index,5) == 0
Send {ENTER}
}
}
Input:
Recurrent major depressive disorder, in partial remission
ICD-9-CM: 296.35
ICD-10-CM: F33.1
ICD-10-CM: F33.2
ICD-9-CM: 296.35
ICD-10-CM: F33.3
ICD-10-CM: F33.4
ICD-9-CM: 296.35
ICD-10-CM: F33.5, ICD-10-CM: X432.6, ICD-10-CM: Y232.6
ICD-10-CM: F33.6
ICD-9-CM: 296.35
Output:
F33.1 F33.2 F33.3 F33.4 F33.5
X432.6 Y232.6 F33.6
Without knowing how the underlying program that you are automating works, I can't tell you when to sleep or send extra ENTERs.
Maybe you can query the state of the screen to determine what to do next (e.g., send codes, extra ENTERs, wait).
I identify the screen state by searching for a small image that uniquely identifies the state the program is in. I make the images using Alt+PrintScrn to capture the entire screen and then use pbrush.exe to crop out small a unique identifying image.
; Search screen for image stored in "images/name.png"
; return true if found, false otherwise
find( name )
{
fname := "images\" . name . ".png"
ImageSearch x, y, 0,0,A_ScreenWidth, A_ScreenHeight, *32 %fname%
return ( ErrorLevel = 0 and x >= 0 and y >= 0 )
}
; Wait for image stored in "images/name.png" to appear on the screen
wait_for( name )
{
tooltip Waiting for %name%, 100,0,1
while !find(name)
sleep 100
}
; business/domain logic bot
automate_screen()
{
if ( find( "logon" ))
do_stuff_to_logon()
else if ( find( "payroll_history" ))
do_some_other_stuff()
else if ( find( "payroll_screen1" ))
{
sendplay Type this on screen1{enter}01{enter}
wait_for( "payroll_screen2" )
sendplay Type this on screen2{enter}
}
}
main()
{
loop
{
automate_screen()
sleep 250
}
}
cls
do
keyed$=inkey$
loop until keyed$<>""
print"the hello world"
end
so in this program as you have seen until a press a key in the statement "keyed$=inkey$" the program wont display the hello world. so i want such a statement or program which will exit the loop in certain time. so the program will wait for 4 second and if users press the key before than that it will go to next line and even if the user wont press any key then program will move to next line in 4 second. please help me!!!
You can use the SLEEP statement:
PRINT "Press a key within 4 seconds to win!"
' Wait 4 seconds or until a key is pressed.
SLEEP 4
' Collect the key pressed, if any.
keyed$ = INKEY$
IF keyed$ <> "" THEN
PRINT "You WON by typing: "; keyed$
ELSE
PRINT "You LOST!"
END IF
Note that certain keys such as the arrow keys are considered "extended" keys. The first character of keyed$ will be equal to CHR$(0) to tell you that an extended key was detected, and the second character will allow you to determine which key it was. You can find more information on the QB64 wiki about those "two byte codes" if you need to handle them. For such keys, the code above won't work so well, as shown below when I press the Up arrow key (CHR$(0) + CHR$(&H48)):
Press a key within 4 seconds to win!
You WON by typing: H
^note the extra space for CHR$(0)
Edit
You can use this instead of a loop to do what you want:
' Wait 4 seconds for a key to be pressed.
SLEEP 4
' If a key was not pressed in 4 seconds, keyed$ = "".
keyed$ = INKEY$
IF keyed$ = "" THEN PRINT "Timeout (no key pressed)"
PRINT "the hello world"
In other words, no loop is needed to detect whether a key was pressed if you use the SLEEP statement and immediately use the INKEY$ function afterward.
If you still prefer a timer-based solution with a loop, then you can use a few extra variables, an additional loop condition, and the TIMER function, which returns the number of seconds elapsed since midnight. The following does the same thing as the SLEEP method above:
maxWait = 4
startTime = TIMER
DO
' Detect a key press.
keyed$ = INKEY$
' Fix the start time if the loop started before midnight
' and the current time is after midnight.
IF startTime > TIMER THEN startTime = startTime - 86400
' Loop until a key is pressed or maxWait seconds have elapsed.
LOOP UNTIL keyed$ <> "" OR startTime + maxWait < TIMER
' If a key was not pressed in 4 seconds, keyed$ = "".
IF keyed$ = "" THEN PRINT "Timeout (no key pressed)"
PRINT "the hello world"
The advantage to this more complex option is the fact that you can do other things in the loop while waiting for a key press. Of course, there is a slight problem. If you're doing too much, the key press will be detected late because INKEY$ won't be called right when the key is pressed. For example:
maxWait = 4
startTime = TIMER
DO
' Do some work.
x = 0
FOR i = 1 TO 1000000
x = x + 1
NEXT i
' Detect a key press and exit the loop if one is detected.
keyed$ = INKEY$
' Fix the start time if the loop started before midnight
' and the current time is after midnight.
IF startTime > TIMER THEN startTime = startTime - 86400
' Loop until a key is pressed or maxWait seconds have elapsed.
LOOP UNTIL keyed$ <> "" OR startTime + maxWait < TIMER
It's tricky to avoid the issue. One option is ON TIMER(n) as suggested by #MatthewWhited, except you can only use one timer event. This means you would need it to exit the loop after maxWait seconds rather than detect a key press, even if there is work occurring. Meanwhile, your key presses will still be detected noticeably later than desired. QB64 allows for the usage of multiple timers, allowing you to handle both. It also allows you to specify n with millisecond precision, unlike QBasic that only allows for 1-second precision at minimum. See ON TIMER(n) on the QB64 Wiki for more information about QB64's improvements to ON TIMER(n).
Since I don't know your usage case, I can't recommend one solution or another, based on the code you've provided.
I want to receive a string from an array using a variables' integer as the array index. But it is not working.
Attempt 1
; Suspended | 0 = No, 1 = Yes
global Suspended := 0
global SuspendedMsg := ["The script has been paused.","The script has been re-activated."]
Pause::
Suspend
if suspended = 0 ; If script is not suspended
{
TrayTip, Paused, SuspendedMsg[Suspended], 3
Suspended++
} else ; If it is suspended
{
TrayTip, Activated, SuspendedMsg[Suspended], 3
Suspended--
}
return
Attempt #1 will just display the string "SuspendedMsg[Suspended]" because I don't know where to set the variable indicator %. Even if I set it to SuspendedMsg[%Suspended%] it will either display [1] or [0].
Attempt 2
; Suspended | 0 = No, 1 = Yes
global Suspended := 0
global SuspendedMsg := ["The script has been paused.","The script has been re-activated."]
global SendSuspendMsg := SuspendedMsg[Suspended]
Pause::
Suspend
if suspended = 0 ; If script is not suspended
{
TrayTip, Paused, %SendSuspendMsg%, 3
Suspended++
} else ; If it is suspended
{
TrayTip, Activated, %SendSuspendMsg%, 3
Suspended--
}
return
Attempt #2 won't do as well, it doesn't even display any message. I tried fiddling arround with % inside the global SendSuspendMsg := SuspendedMsg[Suspended] variable but it won't do no good. Anyone care to help me out?
#Blauhim missed an important point, although his answer is mostly correct. First the Index in an Array when created like you did, always starts at 1, then proceeds to 2 etc, etc... So your code was flawed when you tried to use your Boolean variable to call to an index as a 0 Index does not exist (not to mention that you didn't force and Expression on that TrayTip Command).
; Set our variable to 1 why? Because we are going to use a Logical switch below.
Suspended := 1
; This was correct format and I left it, although I removed Global's as they are not needed
SuspendedMsg := ["The script has been paused.","The script has been re-activated."]
Pause::
; Suspend toggles each time it's called
Suspend
; Here we are toggling the value of our variable using !
; We started with a 1 so that it would be correctly
;Changed to a 0 for the code below.
suspended := !suspended
; Nothing changed here
if suspended = 0 ; If script is not suspended
{
; In order to pass an Array or Object or Expression to a Command you Force it
; using the a Percent Sign with a space on either side.
; Also note you were trying to use your Logical True/False 0 or 1 variable to
; itterate. This didn't work because Array's always start with an Index of 1.
; Below I've accounted for this by simply added a 1 to your suspended so it correctly
; points to the Index in our Array.
TrayTip, Paused, % SuspendedMsg[suspended + 1], 3
} else ; If it is suspended
{
TrayTip, Activated, % SuspendedMsg[suspended + 1], 3
}
return
Instead of TrayTip, Paused, SuspendedMsg[Suspended], 3 or TrayTip, Paused, SuspendedMsg[%Suspended%], 3, try
TrayTip, Paused, % SuspendedMsg[Suspended], 3
. TrayTip asks you for a
specify the message to display
which means as much as a String. So, variables names aren't handled as variables here, but as strings instead (as most of the times in commands). It would make sense to state TrayTip, Paused, %SuspendedMsg[%Suspended%]%, 3
, but you cannot nest variable's percent signs. So, we'll have to use the percent sign to force an expression:
Force an expression: An expression can be used in a parameter that does not directly support it (except OutputVar parameters) by preceding the expression with a percent sign and a space or tab. In [v1.1.21+], this prefix can be used in the InputVar parameters of all commands except the traditional IF commands (use If (expression) instead). This technique is often used to access arrays.
Concerning your second problem: I don't think Arrays can be declared like that, can they..? (but I'm not sure). Also see this short article. So I guess the problem lies within the 3rd line of your code, because the rest of it looks good to me
Referring to Loop (read file contents), a quite strange thing happens every time I use a code like this one to run a script:
^+k::
{
Gosub, MySub
}
Return
MySub:
{
Send, +{Enter}
Loop, read, C:\MyFile.txt
{
temp = %A_LoopReadLine%
Send, %temp%
Send, +{Enter}
}
}
Return
MyFile.txt is a simple text file where sometimes the "plus" symbol (+) is used together with normal letters and numbers.
Despite of this, however, what I see if I run the hotkey on an empty text file, either a Notepad or Microsoft Word blank sheet, is that every + is replaced by an underscore (_), an exclamation mark (!) or a question mark (?). I've seen an occurrence with a dollar symbol ($) replacement, too.
I tried to debug it printing on screen a message box with
MsgBox, %temp%
before sending text and it shows the original content of MyFile.txt perfectly.
Thus the issue should be on Send rather than on file reading.
The content of my file is something like this (repeated for about 20 rows more):
+---------------------------------
120001267381 ~ TEXT 0 10/20/18 VARIABLE word text -> numbers: 17,000 x 108.99 | 109.26 x 15,000 /// number = +5.500% some text
+---------------------------------
120001267381 ~ TEXT 0 10/20/18 VARIABLE word text -> numbers: 17,000 x 108.99 | 109.26 x 15,000 /// number = +5.500% some text
+---------------------------------
120001267381 ~ TEXT 0 10/20/18 VARIABLE word text -> numbers: 17,000 x 108.99 | 109.26 x 15,000 /// number = +5.500% some text
+---------------------------------
120001267381 ~ TEXT 0 10/20/18 VARIABLE word text -> numbers: 17,000 x 108.99 | 109.26 x 15,000 /// number = +5.500% some text
+---------------------------------
What can be the cause of this?
Found the answer: due to the fact that + symbols read from my file are sent like pressing the Shift key, the output is amended by the pressing of such a key instead of sending the original symbol present in file.
In order to send the original content of my file without triggering special hotkeys, I have to use SendRaw instead of Send, like in this example:
^+k::
{
Gosub, MySub
}
Return
MySub:
{
Send, +{Enter}
Loop, read, C:\MyFile.txt
{
temp = %A_LoopReadLine%
SendRaw, %temp%
Send, +{Enter}
}
}
Return
Here's an updated version that pastes using CTRL-V instead of Send to "retype" rows of data:
^+k::
{
Gosub, MySub
}
Return
MySub:
{
Send, +{Enter}
Loop, read, C:\MyFile.txt
{
temp = %A_LoopReadLine%
Clipboard = %temp% ; Write to clipboard
Send, ^v+{enter} ; Paste from clipboard
Sleep 10
; Short delay so it doesn't try to paste again before the clipboard has changed
; This check can get a lot more complex, but just increase it if 10 doesn't work
}
}
Return