Important: The information in this document is obsolete and should not be used for new development.
Supporting Keyboard Navigation of Lists
This section discusses how your application can support keyboard navigation of lists. In particular, this section first shows how your application can respond to the user's typing to select an item in a text-only list. Second, this section shows how your application can respond to the user's pressing of the arrow keys.Supporting Type Selection of List Items
To support type selection of list items, your application must keep a record of the characters the user has typed, the time when the user last typed a character, and which list the last typed character affected. For example, the SurfWriter application defines the following four variables to keep track of this information:
VAR gListNavigateString: {current string being searched} Str255; gTSThresh: Integer; {ticks before type selection resets} gLastKeyTime: LongInt; {time in ticks of last click time} gLastListHit: ListHandle; {last list type selection affected}ThegListNavigateString
variable stores the current status of the type selection. For example, if the user types'h'
and then'e'
and then'l'
and then'l'
and then'o'
, this string should be'hello'
.The
gTSThresh
variable stores the number of ticks after which type selection resets. For example, if the user has typed'hello'
but then waits more than this amount of time before typing'g'
, the SurfWriter application setsgListNavigateString
to'g'
, not to'hellog'
. The value ofgTSThresh
is dependent on the value the user sets for "Delay Until Repeat" in the Keyboard control panel. SurfWriter also resets the type selection if the user begins typing in a different list from the list last typed in. Thus, if the difference between the current tick count and thegLastKeyTime
variable is greater thangTSThresh
, or ifgLastListHit
is not equal to the current list, then the SurfWriter application must reset the type selection.Listing 4-17 shows how the SurfWriter application initializes or resets its type-selection variables.
Listing 4-17 Resetting variables related to type selection
PROCEDURE MyResetTypeSelection; CONST KeyThresh = $18E; {location of low-memory word} kMaxKeyThresh = 120; {120 ticks = 2 seconds} TYPE IntPtr = ^Integer; {for accessing low memory} BEGIN gListNavigateString := ''; {reset navigation string} gLastListHit := NIL; {remember active list} gLastKeyTime := 0; {no keys yet hit} gTSThresh := 2 * IntPtr(KeyThresh)^;{update type-selection } { threshold} IF gTSThresh > kMaxKeyThresh THEN gTSThresh := kMaxKeyThresh; {set threshold to maximum} END;TheMyResetTypeSelection
procedure defined in Listing 4-17 initializes three of the variables to default values and sets thegTSThresh
variable to twice the value of the system global variableKeyThresh
, up to a maximum of 120 ticks. By using the same formula asMyResetTypeSelection
for computing the type-selection threshold, you make sure your application is consistent with other applications as well as with the Finder. The SurfWriter application calls theMyResetTypeSelection
procedure when it starts up and when it wishes to reset the type selection because the type-selection threshold has expired. It also calls the procedure whenever it receives a resume event, because the user might have used the Keyboard control panel, in which case SurfWriter needs to update the value of the type-selection threshold.Having initialized variables related to type selection, the SurfWriter application needs to respond to appropriate key-down events. Listing 4-18 illustrates an application-defined procedure that does this.
Listing 4-18 Selecting an item in response to a key-down event
PROCEDURE MyKeySearchInList (theList: ListHandle; theEvent: EventRecord); VAR newChar: Char; {character to add to search string} theCell: Cell; {cell containing found string} BEGIN newChar := CHR(BAnd(theEvent.message, charCodeMask)); IF (gLastListHit <> theList) OR (theEvent.when - gLastKeyTime >= gTSThresh) OR (Length(gListNavigateString) = 255) THEN MyResetTypeSelection; gLastListHit := theList; {remember list keyed in} gLastKeyTime := theEvent.when; {record time of key-down event} {set length of string} gListNavigateString[0] := Char(Length(gListNavigateString) + 1); {add character to string} gListNavigateString[Length(gListNavigateString)] := newChar; SetPt(theCell, 0, 0); IF LSearch(@gListNavigateString[1], Length(gListNavigateString), @MyMatchNextAlphabetically, theCell, theList) THEN BEGIN {deselect all cells but new cell} MySelectOneCell(theList, theCell); {make sure new selection is visible} MyMakeCellVisible(theList, theCell); END; END;TheMyKeySearchInList
procedure defined in Listing 4-18 first updates variables related to type selection. Then it searches through the list for a cell containing the current search string or for the next cell alphabetically. It searches using theLSearch
function in conjunction with a custom match function defined in Listing 4-15 on page 4-34. The procedure also uses theMySelectOneCell
procedure defined in Listing 4-9 on page 4-27 and theMyMakeCellVisible
procedure defined in Listing 4-10 on page 4-28.
- Note
- If your compiler enforces range checking, you may need to disable it before using the code in Listing 4-18, because the code accesses the length byte of a string directly. See your development system's documentation for more information on range checking.
Supporting Arrow-Key Navigation of Lists
This section discusses how your application can support the use of arrow keys to move the current selection or to extend the current selection using a simple extension algorithm. For information on implementing a more complex anchor algorithm for extending the selection, read this section and then the next section, beginning on page 4-43.The following constants define the ASCII character codes for the various arrow keys. These ASCII values for these keys are the same for U.S. and international keyboards.
CONST kLeftArrow = Char(28); {move left} kRightArrow = Char(29); {move right} kUpArrow = Char(30); {move up} kDownArrow = Char(31); {move down}To support both the moving of a selection (the user's pressing an arrow key without pressing the Shift key) and the extending of a selection (the user's pressing of an arrow key while pressing the Shift key), your application needs to define a routine that computes a new selection location given an old one. For example, if the user presses Command-Left Arrow, the routine should find the cell as far to the left of the first currently selected cell as possible. Listing 4-19 illustrates an application-defined procedure that does this.Listing 4-19 Determining the location of a new cell in response to an arrow-key event
PROCEDURE MyFindNewCellLoc (theList: ListHandle; oldCellLoc: Cell; VAR newCellLoc: Cell; keyHit: Char; moveToExtreme: Boolean); VAR listRows, listColumns: Integer; {list dimensions} BEGIN WITH theList^^.dataBounds DO BEGIN listRows := bottom - top; {number of rows in list} listColumns := right - left; {number of columns in list} END; newCellLoc := oldCellLoc; IF moveToExtreme THEN CASE keyHit OF kUpArrow: newCellLoc.v := 0; {move to row 0} kDownArrow: newCellLoc.v := listRows - 1; {move to last row} kLeftArrow: newCellLoc.h := 0; {move to column 0} kRightArrow: newCellLoc.h := listColumns - 1; {move to last column} END ELSE CASE keyHit OF kUpArrow: IF oldCellLoc.v <> 0 THEN newCellLoc.v := oldCellLoc.v - 1; {row up} kDownArrow: IF oldCellLoc.v <> listRows - 1 THEN newCellLoc.v := oldCellLoc.v + 1; {row down} kLeftArrow: IF oldCellLoc.h <> 0 THEN newCellLoc.h := oldCellLoc.h - 1; {column left} kRightArrow: IF oldCellLoc.h <> listColumns - 1 THEN newCellLoc.h := oldCellLoc.h + 1; {column right} END; END;TheMyFindNewCellLoc
procedure defined in Listing 4-19 computes the coordinates of the cell referenced by thenewCellLoc
parameter based on the coordinates of theoldCellLoc
parameter and the direction of the arrow key pressed. TheoldCellLoc
parameter contains the coordinates of the first or last cell in a selection, depending on which arrow key was pressed. The behavior ofMyFindNewCellLoc
also depends on the value passed in themoveToExtreme
parameter. For example, if the user pressed the Command key while pressing an arrow key, the SurfWriter application passesTRUE
; otherwise, it passesFALSE
. IfmoveToExtreme
isTRUE
, thenMyFindNewCellLoc
returns innewCellLoc
a cell that is as far as possible from the cell specified inoldCellLoc
. Otherwise, it returns a cell that is within one cell ofoldCellLoc
. If a cell cannot be moved in the direction specified by the arrow key,newCellLoc
is equivalent on exit tooldCellLoc
.Having defined the
MyFindNewCellLoc
procedure, it is easy to move or extend a selection in response to an arrow-key event. Listing 4-20 illustrates an application-defined procedure that moves the selection in response to the user's pressing an arrow key without pressing the Shift key.Listing 4-20 Moving the selection in response to an arrow-key event
PROCEDURE MyArrowKeyMoveSelection (theList: ListHandle; keyHit: Char; moveToExtreme: Boolean); VAR currentSelection: Cell; newSelection: Cell; BEGIN IF MyGetFirstSelectedCell(theList, currentSelection) THEN BEGIN IF (keyHit = kRightArrow) OR (keyHit = kDownArrow) THEN {find last selected cell} MyGetLastSelectedCell(theList, currentSelection); {move relative to appropriate cell} MyFindNewCellLoc(theList, currentSelection, newSelection, keyHit, moveToExtreme); {make this cell the selection} MySelectOneCell(theList, newSelection); {make sure new selection is visible} MyMakeCellVisible(theList, newSelection); END; END;TheMyArrowKeyMoveSelection
procedure defined in Listing 4-20 calls theMyFindNewCellLoc
procedure defined in Listing 4-19 to find the coordinates of a cell to select. It computes the coordinates of that new cell relative to the first selected cell if the user pressed a Left Arrow or Up Arrow key; otherwise, it computes the coordinates of the new cell relative to the last selected cell. After computing the coordinates of the new cell,MyArrowKeyMoveSelection
selects it by calling routines defined in
Listing 4-9 and Listing 4-10.Listing 4-21 illustrates an application-defined procedure that extends the selection in response to the user's pressing an arrow key while pressing the Shift key.
Listing 4-21 Extending the selection in response to an arrow-key event
PROCEDURE MyArrowKeyExtendSelection (theList: ListHandle; keyHit: Char; moveToExtreme: Boolean); VAR currentSelection: Cell; newSelection: Cell; BEGIN IF MyGetFirstSelectedCell(theList, currentSelection) THEN BEGIN IF (keyHit = kRightArrow) OR (keyHit = kDownArrow) THEN {find last selected cell} MyGetLastSelectedCell(theList, currentSelection); {move relative to appropriate cell} MyFindNewCellLoc(theList, currentSelection, newSelection, keyHit, moveToExtreme); {add a new cell to the selection} IF NOT LGetSelect(FALSE, newSelection, theList) THEN LSetSelect(TRUE, newSelection, theList); {make sure new selection is visible} MyMakeCellVisible(theList, newSelection); END; END;TheMyArrowKeyExtendSelection
procedure defined in Listing 4-21 works just
like theMyArrowKeyMoveSelection
procedure defined in Listing 4-20, but it does not deselect all other cells besides the newly selected cell.Listing 4-22 shows an application-defined procedure that takes advantage of the
code listings provided in this section. The SurfWriter application calls the procedure in
Listing 4-22 every time it receives an arrow-key event that affects a list.Listing 4-22 Processing an arrow-key event
PROCEDURE MyArrowKeyInList (theList: ListHandle; theEvent: EventRecord; allowExtendedSelections: Boolean); BEGIN IF (NOT allowExtendedSelections) OR (BAnd(theEvent.modifiers, shiftKey) = 0) THEN MyArrowKeyMoveSelection(theList, CHR(BAnd(theEvent.message, charCodeMask)), BAnd(theEvent.modifiers, cmdKey) <> 0) ELSE MyArrowKeyExtendSelection(theList, CHR(BAnd(theEvent.message, charCodeMask)), BAnd(theEvent.modifiers, cmdKey) <> 0); END;TheMyArrowKeyInList
procedure defined in Listing 4-22 takes three parameters, the third of which is a Boolean variable that indicates whether the application supports the use of Shift-arrow key combinations to extend the current selection. If the application does support this and the user held down the Shift key, theMyArrowKeyInList
procedure calls the procedure in Listing 4-21 to extend the selection. Otherwise, it calls the procedure in Listing 4-20 to move the selection. Either way, it checks the status of the Command key to determine whether the appropriate procedure should move as far in the direction of the arrow key as possible before selecting a new cell.Supporting the Anchor Algorithm for Extending Lists With Arrow Keys
This section summarizes how your application can support the anchor method for extending lists with arrow keys. Implementing this method takes a lot of work, but the extra work may pay off if you expect many users of your application's lists to make range selections or if your application uses multicolumn lists. For a comparison between the anchor algorithm and the extension algorithm illustrated in the previous section, see "Extension of a Selection With Arrow Keys" on page 4-10.To support the anchor algorithm, your application must keep track of several types of information between Shift-arrow key events. Most importantly, your application must store information about which cell in a list is the anchor cell and which cell is the moving cell. In response to a Shift-arrow key event, your application should change the location of the moving cell. It should then highlight all cells in the rectangle whose corners are the anchor cell and the moving cell. This permits the user to use several consecutive Shift-arrow key combinations to move a rectangular range of cells around the anchor cell.
Your application must thus save the location of the anchor cell the first time the user uses a Shift-arrow key combination to affect a certain rectangular range of cells. For example, if the user presses Shift-Right Arrow and the user has not before used a Shift-arrow key combination, then your application should store as the anchor cell the upper-left cell in the rectangular range of cells to be affected. The moving cell is then one cell to the right of what was the lower-right corner of this range.
Your application can determine what rectangular range of cells a Shift-arrow key combination is meant to affect by using the
LLastClick
function, which returns the coordinates of the last cell that was clicked. (If your application relies on this function, it must always update thelastClick
field of the list record in response to keyboard selection of any list item, since keyboard selection of a list item is functionally equivalent to clicking.) Your application must check the selection status of adjacent cells to find as big a rectangular range of selected cells surrounding this cell as possible.Your application can check whether a Shift-arrow key event is affecting a new range of cells simply by checking the
clikTime
field of the list record. (Your application must thus also update this field in response to keyboard selection of any list item.) If the last click time changes between Shift-arrow key events, your application knows that the user has clicked the list or used the keyboard to change the selection. In this case, your application must compute a new anchor cell and moving cell based on theLLastClick
function and the direction of the arrow key pressed. Otherwise, your application can keep the same anchor cell, move the moving cell in the direction specified by the arrow key, and highlight cells in the rectangular range of the anchor cell and the moving cell.In summary, if your application is to support the anchor algorithm for extending a list selection, it must keep track of an anchor cell, a moving cell, and the time of the last click in a list. (Your application might store a handle to a relocatable block containing this information in the
userHandle
field of the list record.) Whenever a Shift-arrow key event is meant to affect a new range of cells, your application updates all three of these variables. Otherwise, it only changes the coordinates of the moving cell from one Shift-arrow key event to the next.