Jump to content


Photo

Search as you type script breaks if type to fast


  • Please log in to reply
11 replies to this topic

#1 stressbaby

stressbaby
  • Members
  • 150 posts

Posted 14 June 2012 - 12:16 AM

I have a script that adds a search-as-you-type functionality to an application.

With help from this forum, I have made each key on the keyboard a context-sensitive hotkey. With each entered letter, the script clicks the search button and returns cursor focus to the search text box using a Controlclick command. When this happens, the entire entry is highlighted. To get the cursor to the end of the entry in the textbox, I have to send right clicks so that the cursor is properly positioned for the next keystroke.

It works fine if the user types slowly. The problem comes when the user types too fast. What happens is that the second hotkey is sent while the entry is still highlighted, before the "SendInput {right 20}" is executed. So if I type slowly A-N-D-E-R-S-O-N it properly searches for "Anderson." But if I type slowly A-N-D-E-R then type S-O-N really fast, the "O" hotkey is sent while the "ANDERS" is still highlighted. So "ANDERS" is replaced by "ON" and the script searches for last names starting with "ON."

I have tried making the subroutine Critical, that didn't help. Any suggestions are welcome. This is an addon I wrote for an electronic health record and some docs would like the functionality improved. Thanks in advance.

The hotkeys:
string = ; I think this is a legacy from an older script
length = 0
k_n = 1
k_ASCII = 45

Hotkey, IfWinActive, Patient Lookup		; makes hotkeys context-sensitive Diagnosis Code window
Hotkey, ~*Enter, k_LookupEnter, on
Hotkey, ~*Backspace, k_Lookup, on
Hotkey, ~*Delete, k_Lookup, on
Hotkey, ~*numpad0, k_Lookup, on
Hotkey, ~*numpad1, k_Lookup, on
Hotkey, ~*numpad2, k_Lookup, on
Hotkey, ~*numpad3, k_Lookup, on
Hotkey, ~*numpad4, k_Lookup, on
Hotkey, ~*numpad5, k_Lookup, on
Hotkey, ~*numpad6, k_Lookup, on
Hotkey, ~*numpad7, k_Lookup, on
Hotkey, ~*numpad8, k_Lookup, on
Hotkey, ~*numpad9, k_Lookup, on

Loop
{
	Transform, k_char, Chr, %k_ASCII%
	StringUpper, k_char, k_char
	if k_char not in <,>,^,~,,`,
		Hotkey, ~*%k_char%, k_Lookup, on	
		; In the above, the asterisk prefix allows the key to be detected regardless
		; of whether the user is holding down modifier keys such as Control and Shift.
	if k_ASCII = 93
		break
	k_ASCII++
}
Hotkey, IfWinActive					; turn off context sensitivity

The subroutine:
k_LookupEnter:
ControlClick, ThunderRT6CommandButton18, ahk_class ThunderRT6FormDC,,,2
return

k_Lookup:
ControlGetFocus, focus, Patient Lookup
If ((focus <> "ThunderRT6TextBox18") && (focus <> "ThunderRT6TextBox19"))
{
 return
}
ControlClick, ThunderRT6CommandButton5, ahk_class ThunderRT6FormDC,,,2 ; clicks search
If (focus = "ThunderRT6TextBox18")
{
 ControlClick, ThunderRT6TextBox18, ahk_class ThunderRT6FormDC,,,2 ; returns focus to the last name textbox
}
If (focus = "ThunderRT6TextBox19")
{
 ControlClick, ThunderRT6TextBox19, ahk_class ThunderRT6FormDC,,,2 ; returns focus to the first name textbox
}
SendInput {right 20} ; textboxes are highlighted, so it is necessary to send right to move cursor to the end
return


#2 engunneer

engunneer
  • Fellows
  • 9162 posts

Posted 14 June 2012 - 01:03 AM

an interesting approach

would you consider using input to collect the data (instead of hotkeys)? you could maybe use it with ControlSetText to set the field.

Also, can you send {end} instead of {right 20}?

Lastly, toss a SetBatchLines, -1 at the top of the script and maybe a Critical at the start of k_lookup to make ahk care more about speed.

#3 Alpha Bravo

Alpha Bravo
  • Members
  • 853 posts

Posted 14 June 2012 - 02:55 AM

Please see if the following works for your, it will suppress the execution of k_lookup until you stop typing for a period of time and a minimum key strokes are pressed.
the SearchWord/tooltip lines were just for sake of testing
string = ; I think this is a legacy from an older script
length = 0
k_n = 1
k_ASCII = 45

;~ Hotkey, IfWinActive, Patient Lookup      ; makes hotkeys context-sensitive Diagnosis Code window
Hotkey, ~*Enter, k_LookupEnter, on
Hotkey, ~*Backspace, k_Lookup, on
Hotkey, ~*Delete, k_Lookup, on
Hotkey, ~*numpad0, k_Lookup, on
Hotkey, ~*numpad1, k_Lookup, on
Hotkey, ~*numpad2, k_Lookup, on
Hotkey, ~*numpad3, k_Lookup, on
Hotkey, ~*numpad4, k_Lookup, on
Hotkey, ~*numpad5, k_Lookup, on
Hotkey, ~*numpad6, k_Lookup, on
Hotkey, ~*numpad7, k_Lookup, on
Hotkey, ~*numpad8, k_Lookup, on
Hotkey, ~*numpad9, k_Lookup, on

Loop
{
   Transform, k_char, Chr, %k_ASCII%
   StringUpper, k_char, k_char
   if k_char not in <,>,^,~,,`,
      Hotkey, ~*%k_char%, [color=#FF4000]StartTimer[/color], on   
      ; In the above, the asterisk prefix allows the key to be detected regardless
      ; of whether the user is holding down modifier keys such as Control and Shift.
   if k_ASCII = 93
      break
   k_ASCII++
}
;~ Hotkey, IfWinActive               ; turn off context sensitivity
return

k_LookupEnter:
SearchWord := Keys := ""
ToolTip
; the rest of k_LookEnter code here
return

StartTimer:
Keys++
SearchWord .= SubStr(A_ThisHotkey, 3)
SetTimer, k_Lookup, 50
return

k_Lookup:
ToolTip % "time = " A_TimeSincePriorHotkey
; adjust "A_TimeSincePriorHotkey" and "Keys" to suit your needs. minimum keys should be 2
if (A_TimeSincePriorHotkey < [color=#00BF00]800[/color]) || (Keys < [color=#00BF00]2[/color])	
	return
SetTimer, k_Lookup, Off
ToolTip % SearchWord "`n" Keys
; the rest of k_Lookup code here
return


#4 stressbaby

stressbaby
  • Members
  • 150 posts

Posted 14 June 2012 - 06:55 PM

Thanks for the replies. Unfortunately no luck with these suggestions.
AB, your changes do stop the script from executing until a given wait period, but if you happen to hit the next key just as the wait period ends, the letters are replaced.
Engunneer, I didn't see any changes in behavior from the changes you suggested.

I think the hotkey threads need to be buffered. However, the tilde (~) sends the original key, and I'm thinking that the execution of this part of the command is not strictly speaking part of the thread. I'm thinking that somehow the keystroke sent needs to be included in the thread to keep this from occurring. Other thoughts? Or thoughts on how to do that?

#5 Rseding91

Rseding91
  • Members
  • 664 posts

Posted 14 June 2012 - 09:25 PM

You could have each hotkey trigger a small wait timer (settimer, -50) so it waits until you stop typing (or pause for a small amount of time) before hitting the search button.

#6 Alpha Bravo

Alpha Bravo
  • Members
  • 853 posts

Posted 14 June 2012 - 09:36 PM

Depending on the application you are running, if your can find out if searching is complete, you may want to try the following
k_Lookup:

ToolTip % "time = " A_TimeSincePriorHotkey

[color=#40BF00]if Processing

	return[/color]

; adjust "A_TimeSincePriorHotkey" and "Keys" to suit your needs. minimum keys should be 2

if (A_TimeSincePriorHotkey < 800) || (Keys < 2)   

   return

[color=#40BF00]Processing = 1[/color]

SetTimer, k_Lookup, Off

ToolTip % SearchWord "`n" Keys

; the rest of k_Lookup code here



[color=#40BF00]while !( [color=#FF0000]search complete condition[/color] )	; wait for search to complete

	Sleep, 100

Processing = [/color]

return


#7 stressbaby

stressbaby
  • Members
  • 150 posts

Posted 17 June 2012 - 11:44 AM

AB, thanks, I will try it.
The root issue here seems to me to be that the key's native function is not blocked. I can sleep the thread, stall the thread, make the thread critical, but no matter what I do with the threads, if the user types another letter, that key is sent.
What it seems like I need is a way to reliably buffer all keyboard input, not just the thread.

#8 Maestr0

Maestr0
  • Members
  • 649 posts

Posted 17 June 2012 - 12:54 PM

Have you tried a SetTimer in the subroutine? Like this:
k_LookupEnter:
ControlClick, ThunderRT6CommandButton18, ahk_class ThunderRT6FormDC,,,2
return

k_Lookup:
	SetTimer, k_timer, -200 ; wait 200 ms before executing k_lookup
return
k_timer:
ControlGetFocus, focus, Patient Lookup
If ((focus <> "ThunderRT6TextBox18") && (focus <> "ThunderRT6TextBox19"))
{
 return
}
ControlClick, ThunderRT6CommandButton5, ahk_class ThunderRT6FormDC,,,2 ; clicks search
If (focus = "ThunderRT6TextBox18")
{
 ControlClick, ThunderRT6TextBox18, ahk_class ThunderRT6FormDC,,,2 ; returns focus to the last name textbox
}
If (focus = "ThunderRT6TextBox19")
{
 ControlClick, ThunderRT6TextBox19, ahk_class ThunderRT6FormDC,,,2 ; returns focus to the first name textbox
}
SendInput {right 20} ; textboxes are highlighted, so it is necessary to send right to move cursor to the end
return
update: this is basically what rseding said

#9 stressbaby

stressbaby
  • Members
  • 150 posts

Posted 17 June 2012 - 01:06 PM

Thanks Maestr0, yes I did that, and it didn't help. Even if the script waits 200ms, if the next keystroke comes at 201ms, then that hotkey gets sent and the text is replaced.

This is what I'm working with at the moment. I removed the tilde that sends the native keystroke, going more with engunneer's approach:
length = 0
k_n = 1
k_ASCII = 45

Hotkey, IfWinActive, Patient Lookup		; makes hotkeys context-sensitive Diagnosis Code window
Hotkey, ~*Enter, k_LookupEnter, on
Hotkey, Backspace, k_LookupBack, on
Loop
{
	Transform, k_char, Chr, %k_ASCII%
	StringUpper, k_char, k_char
	if k_char not in <,>,^,~,,`,
		Hotkey, %k_char%, k_Lookup, on	
		; In the above, the asterisk prefix allows the key to be detected regardless
		; of whether the user is holding down modifier keys such as Control and Shift.
	if k_ASCII = 93
		break
	k_ASCII++
}
Hotkey, IfWinActive					; turn off context sensitivity

; Patient Lookup subroutines
k_LookupEnter:
ControlClick, ThunderRT6CommandButton18, ahk_class ThunderRT6FormDC,,,2
return

k_LookupBack:
ControlGetFocus, focus, Patient Lookup
If ((focus <> "ThunderRT6TextBox18") && (focus <> "ThunderRT6TextBox19"))
{
 return
}
ControlGetText, entry, ThunderRT6TextBox18
StringTrimRight, entry, entry, 1
Tooltip, %entry%
ControlSetText, ThunderRT6TextBox18, %entry%, ahk_class ThunderRT6FormDC
ControlClick, ThunderRT6CommandButton5, ahk_class ThunderRT6FormDC,,,2 ; clicks search
If (focus = "ThunderRT6TextBox18")
{
 ControlClick, ThunderRT6TextBox18, ahk_class ThunderRT6FormDC,,,2
}
If (focus = "ThunderRT6TextBox19")
{
 ControlClick, ThunderRT6TextBox19, ahk_class ThunderRT6FormDC,,,2
}
SendInput {end}
return

k_Lookup:
Critical
key := A_ThisHotKey
ControlGetFocus, focus, Patient Lookup
If ((focus <> "ThunderRT6TextBox18") && (focus <> "ThunderRT6TextBox19"))
{
 return
}
ControlGetText, entry, ThunderRT6TextBox18
entry := entry . key
Tooltip, %entry%
ControlSetText, ThunderRT6TextBox18, %entry%, ahk_class ThunderRT6FormDC
ControlClick, ThunderRT6CommandButton5, ahk_class ThunderRT6FormDC,,,2 ; clicks search
If (focus = "ThunderRT6TextBox18")
{
 ControlClick, ThunderRT6TextBox18, ahk_class ThunderRT6FormDC,,,2
}
If (focus = "ThunderRT6TextBox19")
{
 ControlClick, ThunderRT6TextBox19, ahk_class ThunderRT6FormDC,,,2
}
SendInput {end}
return

This is better in the sense that entries are not removed. However, when typing fast, some letters are missed. For example, if I type A-N-D-E-R then type S-O-N fast, I might get "ANDERSN" or "ANDERSO"

Any thoughts on performance improvements using this approach?

#10 just me

just me
  • Members
  • 1174 posts

Posted 18 June 2012 - 05:58 AM

If the above is your whole script, you might try to insert
#NoEnv
SetBatchlines, -1
SetControlDelay, -1
on top of your script (and remove Critical in k_Lookup:.

Also you might try to use BlockInput during execution of k_Lookup: to prevent the loss of input. At last I'd think that removing the tilde isn't the best idea in matters of performance.

#11 Ronins

Ronins
  • Members
  • 115 posts

Posted 18 June 2012 - 08:48 AM

Why not use something like this?

Save this as init.ini in same folder as script
[General]
Item1 = anderson
Item2 = superman
Item3 = Flash
Item4 = AutoHotkey

And use this as .ahk file..
Loop{
	IniRead, item, init.ini, General, item%A_Index%
	if item = error
		break
	cmd = %cmd%%item%`|
}
Gui, Font, s10 italic, %Font%
Gui, Add, Text, cd2d2d2 vStat
GuiControl, Font, Edit1
Gui, Add, Edit, y25 x0 w270 vEdit1 gText
Gui, Add, Listbox, vOptions hidden w270 x0 y50

Gui, show,, Top
ControlFocus, Edit1, Top
return

GuiClose:
ExitApp

Text:
Gui, Submit, nohide
if Edit1 = 
{
	GuiControl, Hide, Options
	return
}
GuiControl,, Options, |
basestr =
Loop, parse, cmd, |
IfInString, A_LoopField, %Edit1%
	basestr = %basestr%`|%A_LoopField%
GuiControl,, Options, %basestr%
GuiControl, show, Options
return
Lemme know if it works out for u

#12 Roninz

Roninz
  • Guests

Posted 18 June 2012 - 09:01 AM

Oh crap.. I was trying smthing with it, and forgot to strip those lines.. Well .ahk will work good, but u can strip off some lines..