[jawsscripts] Re: Implementing first-letter navigation

  • From: Udo Egner-Walter <udo.egner-walter@xxxxxx>
  • To: jawsscripts@xxxxxxxxxxxxx
  • Date: Mon, 9 Nov 2020 19:08:52 +0100

Hi Sean, 

you asked about the hook and I will insert a step by step instruction. But 
meanwhile I thought about your problem and now think it could be done without 
using a hook. 

You could use the TrapKeys function of JAWS. This "traps" all the keys not 
assigned to scripts. Since you are using only one letter keystrokes this should 
be fine for you. I've tried this out myself on Explorer and inserted the code I 
used too (at the end of the mail). 

The difference between the TrapKey function and the Hook is that you trapping 
keys not attached to scripts with TrapKey and on the other hand have full 
control over scripts with the hook function. 

First here are the steps for using a hook: 

1. Make a script to start the hook
 
Script StartHook ()
AddHook (HK_SCRIPT, "KeyboardHookFunction")
EndScript
 
2. Make a script to stop the hook
 
Script StopHook ()
RemoveHook (HK_SCRIPT, "KeyboardHookFunction")
EndScript
 
3. Make a function that is called every time a script was executed 

After starting the hook the function we made as the second parameter 
("KeyboardHookFunction") is called every time we start a script. In this 
function we can decide if we want a script to run or to stop. If we return true 
the script is allowed to run and if we return false the script is stopped. 
 
int Function KeyboardHookFunction (string ScriptName)
If ScriptName == "StopHook" then
SayString ("Stop script now")
Return True
EndIf
 
; All other Script are not allowed 
Return False
EndFunction
 
4. Trap the keys

To trap the keys not assigned to scripts we start trapping in the start hook 
script and stop trapping in the stop hook script: So we add the lines to the 
script: 
 
 
Script StartHook ()
AddHook (HK_SCRIPT, "KeyboardHookFunction")
; trap the keys
TrapKeys (True, True)
EndScript
 
Script StopHook ()
RemoveHook (HK_SCRIPT, "KeyboardHookFunction")
; Don't trap the keys any longer 
TrapKeys (False)
EndScript
 
5. React on key pressing

To react to keys being pressed we should remember if the hook function is 
running. Therefore we save this in a global variable: 
 
globals
int giHookActive
 
Script StartHook ()
AddHook (HK_SCRIPT, "KeyboardHookFunction")
TrapKeys (True, True)
; remember the hook is active 
giHookActive = True
EndScript
 
Script StopHook ()
RemoveHook (HK_SCRIPT, "KeyboardHookFunction")
TrapKeys (False)
; remember the hook is no longer active 
giHookActive = False
EndScript

6. No we can use a key press event of JAWS to react on keys being pressed: 

Void Function KeyPressedEvent (int nKey, string strKeyName, int nIsBrailleKey, 
int nIsScriptKey)
 
If giHookActive then
; speak the key being pressed
SayString (strKeyName)
EndIf
 
; react on pressing Escape (only if 
; the hook is active) 
if strKeyName == "Escape" && giHookActive then
PerformScript StopHook ()
EndIf
 
; pass the key pressed event to other functions 
KeyPressedEvent (nKey,strKeyName,nIsBrailleKey,nIsScriptKey)
EndFunction

These are the steps if you want to use a keyboard hook. 

Now a little example of doing one-letter navigation (here Explorer in Windows). 
If there are many tree items the script will be very slow. But it should give 
you a hint how to do this: 

<Code start>

globals
int giTrapKeys

; Script to activate trapping of keys 
Script TrapMyKeys ()
; trap keys not assigned to a script 
TrapKeys (True)
; say the trap mode is on
; (you can delete the line after testing the script
; if you want) 
SayString ("trapping keys")
; remember in a global variable that 
; trapping keys is running
giTrapKeys = True
EndScript

; script to deactivate trapping of keys 
Script DontTrapMyKeys ()
; Don't trap keys any longer 
TrapKeys (False)
; say that trapping keys is ending 
SayString ("trapping keys is off")
; and remember this in a global variable 
giTrapKeys  = False
EndScript

; function is called every time a key is pressed 
Void Function ProcessKeyPressed (int nKey, string strKeyName, int 
nIsBrailleKey, int nIsScriptKey)
var
int iCharValue
 
; If we are trapping keys 
If giTrapKeys then
            ; check if the string is 1 char of length
            If StringLength (strKeyName) == 1 then
                ; Get ASCII-value of this char
                 iCharValue = GetCharacterValue (strKeyName)
                        ; If the ASCII value is between A and Z 
                        If (iCharValue > 64) && (iCharValue < 91)
                        ; run search function (with char as parameter)
                        DoCharSearch (strKeyName)
                        EndIf ; If (iCharValue > 64) && (iCharValue < 91)
            EndIf ; If StringLength (strKeyName) == 1 then
EndIf
; pas event to other functions 
ProcessKeyPressed(nKey,strKeyName,nIsBrailleKey,nIsScriptKey)
EndFunction
 
; search function 
void Function DoCharSearch (string sChar)
var
object oUIA = CreateObjectEx ("FreedomSci.UIA", 0, "UIAScriptAPI.x.manifest"),
object oFocusElement = oUIA.GetFocusedElement().BuildUpdatedCache(),
object oElement = oUIA.GetElementFromHandle(GetAppMainWindow (GetFocus())),
object oElements,
object oCondition,
object oEntry,
int iBegin = False,
int i
 
; search for tree items
oCondition = oUIA.CreateIntPropertyCondition (UIA_ControlTypePropertyId, 
UIA_TreeItemControlTypeId)
; search the whole app 
oElements = oElement.FindAll (TreeScope_Descendants, oCondition)
 
; Loop over all tree items 
; (For each loop only runs the first time so later we use 
; for loop) 
ForEach oEntry in oElements
; Since we are search at the current position 
; we will begin if "iBegin" is set to true
            if iBegin then
                        ; If the first char of the name is our searched char 
                        If StringLeft (oEntry.Name, 1) == sChar then
                        ; focus it
                        oEntry.SetFocus ()
                        SayString ("found")
                        ; and leave the funciton 
                        Return
                        EndIf
            EndIf ; If iBegin then

; here we check if the item of the loop is 
; the item being focused if we started the script 
If oUIA.CompareElements (oFocusElement, oEntry) then
; If yes then set iBegin to true 
iBegin = True
EndIf ; If oUIA.CompareElements (oFocusElement, oEntry) then
EndForEach

; if the function still runs here we didn't find something 
; beneath the focused item so we start the 
; search at the beginning 
SayString ("Nothing found yet")
; This time we use a for loop because 
; ForEach loop only runs one time in a script 
for i=0 to oElements.Count-1
                        ; if the first char of the name is our search char 
                        If StringLeft (oElements(i).Name, 1) == sChar then
                        ; focus it 
                        oElements(i).SetFocus ()
                        SayString ("gefunden")
                        ; and leave the function 
                        Return
                        EndIf
EndFor
; if the function is still running to this point 
; we have found nothing 
SayString ("Nothing found in the whole tree")
EndFunction

; Here we use a FocusChangedEvenht to 
; automatically start the script and stop it 

void function FocusChangedEventEx (
            handle hwndFocus, int nObject, int nChild,
            handle hwndPrevFocus, int nPrevObject, int nPrevChild,
            int nChangeDepth)
var
object oUIA = CreateObjectEx ("FreedomSci.UIA", 0, "UIAScriptAPI.x.manifest"),
object oFocusElement = oUIA.GetFocusedElement().BuildUpdatedCache()

; if the control type of the focused element is a tree item 
If oFocusElement.ControlType == UIA_TreeItemControlTypeId then
; we can start the trapping of keys 
PerformScript TrapMyKeys ()
else
; in the other case stop trapping of keys 
PerformScript DontTrapMyKeys ()
EndIf
; Pass event to other functions 
FocusChangedEventEx (hwndFocus, nObject, nChild, hwndPrevFocus, nPrevObject, 
nPrevChild, nChangeDepth)
EndFunction

; And very important 
; Stop trapping of keys if you leave your app 

void Function AutoFinishEvent ()
PerformScript DontTrapMyKeys ()
EndFunction

<Code End>

Hope this helps. And I hope the mails clients didn't mix the lines so the 
compiler is angry ðŸ˜‰ 
Udo 



Am 08.11.2020 um 15:24 schrieb Sean Farrow <sean.farrow@xxxxxxxxxxxxxxxx>:

Hi,
The tree view is a custom control, using the .Net framework and more 
specifically WinForms. It kind of supports MSAA, so can be traversed.

A hook sounds like the best bet, but it would be good to find out more about 
how you'd do it.
Thanks,
Sean.
-----Original Message-----
From: jawsscripts-bounce@xxxxxxxxxxxxx <jawsscripts-bounce@xxxxxxxxxxxxx> On 
Behalf Of Udo Egner-Walter
Sent: 08 November 2020 08:36
To: jawsscripts@xxxxxxxxxxxxx
Subject: [jawsscripts] Re: Implementing first-letter navigation

Hi Sean, 

there are some ways to do this and all of them have some advantages and 
disadvantages. Here are some thoughts about this: 

First of all the question is this a SysTreeView32 or another control? Does 
the treeview support UIA? 

In case of first-letter navigation there are some ways: 

1. Using one of the JAWS keyboard events or keyboard event helpers, for 
example KeyPressedEvent (). So you can react on the pressing of a specific 
key. One disadvantage is here that you can't stop sending the key to the app. 
If you app uses a key being pressed the app will execute the command assigned 
to the key. For single keystrokes this rather unusual but you should keep 
this in mind. 

2. You can install a keyboard hook. If you do this you can trap all keys and 
decide in your script which one should pass and which one not. One 
disadvantage here is you might not be able to stop the keyboard hook and the 
keys and scripts being trapped and you can't work no longer till you release 
the keyboard hook. 

3. You can implement some kind of virtual keys. If you do this your scripts 
control the keys till you are pressing a key to end this virtual mode. I know 
two ways of doing this: 

a) Using the layered keys in combination with a * at the end of the key. So 
the keys are still active till you press a key without * in the layered keys 
section. 

b) Load another key section from an ini-file where you can assign single keys 
to you scripts. 

This is only an overview and the possibilities I know. if you want more 
details please let me know and I try to explain this in details and with 
examples. 

But implementing the first-letter navigation makes only sense if you can 
focus your tree nodes by script. Therefore my questions at the beginning of 
the mail. 

Hope this helps
Udo 


Am 07.11.2020 um 17:01 schrieb Sean Farrow <sean.farrow@xxxxxxxxxxxxxxxx>:

Hi all,
I've got a tree view that does not natively implement first-letter 
navigation, what is the best way of doing this effectively.

Any help appreciated.

Thanks,
Sean.


__________ 

View the list's information and change your settings at 
http://www.freelists.org/list/jawsscripts


__________ 

View the list's information and change your settings at 
http://www.freelists.org/list/jawsscripts

__________�

View the list's information and change your settings at 
http://www.freelists.org/list/jawsscripts


__________�

View the list's information and change your settings at 
http://www.freelists.org/list/jawsscripts

Other related posts: