Focus management and keyboard control are two related topics in Flex. An object has focus when it can respond to keyboard events. For example, when a text input control has focus the user can enter text into the field. When a component has focus, it generally indicates that focus with a colored border. You can use the keyboard to control focus within a Flex application, and you can also respond to key presses. We’ll look at all of these topics in the next few sections.
A standard convention of application usability is that pressing the Tab key advances the focus to the next element and Shift-Tab moves focus to the preceding element. This is true of most desktop applications. It is true of most HTML applications. And it is also true of Flex applications.
Many (though certainly not all) Flex components are capable of
receiving focus. For example, text inputs, combo boxes, and buttons are all
capable of receiving focus. Clearly, there are other types of components
that cannot receive focus. For example, a VBox
container, a label, or a spacer cannot
receive focus because none of these components is capable of responding
to keyboard input.
When several focus-enabled components exist on the screen at the
same time, there exists a default order by which the user can move focus
by pressing the Tab key. The default tab order follows the order in
which components were initialized. The following code creates a form
with three text inputs and a button. The first two text inputs are side
by side on the same line, then the next text input follows on the next
line, and that is followed by the button on the next line. In this
example, if the user places focus in the firstName
text input and then presses the Tab
key, the focus will next move to the lastName
text field. Another press of the Tab
key and focus will shift to the email
text input on the next line. Finally, one more Tab key press and focus
will move to the button.
<mx:Form> <mx:FormItem label="Name"> <mx:HBox> <mx:TextInput id="firstName" /> <mx:TextInput id="lastName" /> </mx:HBox> </mx:FormItem> <mx:FormItem label="Email"> <mx:TextInput id="email" /> </mx:FormItem> <mx:FormItem label=""> <mx:Button label="Submit" /> </mx:FormItem> </mx:Form>
If the user presses Tab again with focus on the button, focus will
return to the first item: the firstName
text input. This is known as a
tab loop, because pressing the Tab key shifts focus
from component to component in a circular or looping fashion.
Although the default order of elements in a tab loop is generally
what you would want and what a user of the application would expect,
there are exceptions. For those exceptions you can control the order of
the elements in a tab loop by specifying tabIndex
property values. Every focus-enabled component has a tabIndex
property. By default, the properties
are null
, and Flex applications use
the default tab order. However, you can explicitly define the order of
the elements in a tab loop by specifying incrementing integer values
(starting with 1) for the tabIndex
properties of all the components in a tab loop. The following example
illustrates how this works with text inputs arranged in a grid. By
default, the order would go from left to right, top to bottom. In this
case, we’re setting the tabIndex
properties so that the order is from top to bottom, left to
right.
<mx:Grid> <mx:GridRow width="100%" height="100%"> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="a" tabIndex="1" /> </mx:GridItem> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="c" tabIndex="3" /> </mx:GridItem> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="e" tabIndex="5" /> </mx:GridItem> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="g" tabIndex="7" /> </mx:GridItem> </mx:GridRow> <mx:GridRow width="100%" height="100%"> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="b" tabIndex="2" /> </mx:GridItem> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="d" tabIndex="4" /> </mx:GridItem> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="f" tabIndex="6" /> </mx:GridItem> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="h" tabIndex="8" /> </mx:GridItem> </mx:GridRow> </mx:Grid>
The behavior of the tab order can be unpredictable if you set
one or more, but not all, of the tabIndex
properties for the components in a
tab loop. Generally, you should either use the default tab order or
set the tabIndex
for all the
components in a tab loop.
Although focus-enabled components are included in the tab order by
default, you can explicitly exclude them by setting their tabEnabled
property to false
. Setting tabEnabled
to false
does not mean the component cannot
receive focus programmatically or when the user clicks on it with the
mouse. However, it does mean that it will not be included in the tab
loop.
If you want to exclude all the child components of a container
from a tab loop, you can simply set the tabChildren
property of the container to
false
. That has the same effect as setting tabEnabled
to false
for each of the child controls.
You can control focus programmatically using an mx.managers.FocusManager
instance. A FocusManager
instance
controls one tab loop, and at some point multiple tab loops may exist
per application. This is so that some containers are capable of creating
their own tab loops. For example, a pop-up window might have its own tab
loop, distinct from the tab loop in a form behind the window. Because
several tab loops might contain elements visible at the same time, a
Flex application can have more than one FocusManager
in use at any one
time.
You never have to construct a new FocusManager
instance. Every component has a
focusManager
property that references the FocusManager
instance that controls the tab
loop to which that component belongs.
You can programmatically retrieve the focused item using the
getFocus()
method of a FocusManager
instance. The getFocus()
method returns the component that
currently has focus typed as IFocusManagerComponent
. You should cast the
return of getFocus()
when necessary.
Unlike lower-level Flash Player APIs for focus management, the getFocus()
method of a FocusManager
instance always returns a
reference to the actual component that has focus, not a raw child object
of the component. For example, from a Flash Player perspective, when a
text input control has focus, it is really the nested lower-level text
field that has focus. Yet from a practical standpoint, you are usually
interested in the component that has focus, not its subelements.
You can set focus using the setFocus()
method of a FocusManager
object. You can pass setFocus()
a reference to any focus-enabled
component. For example, the following code resets the values of the text
input controls and then moves focus to the first text input when the
user clicks the button:
<mx:Script> <![CDATA[ private function reset(event:Event):void { a.text = ""; b.text = ""; c.text = ""; d.text = ""; focusManager.setFocus(a); } ]]> </mx:Script> <mx:VBox height="100%"> <mx:Grid> <mx:GridRow width="100%" height="100%"> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="a" tabIndex="1" /> </mx:GridItem> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="c" tabIndex="3" /> </mx:GridItem> </mx:GridRow> <mx:GridRow width="100%" height="100%"> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="b" tabIndex="2" /> </mx:GridItem> <mx:GridItem width="100%" height="100%"> <mx:TextInput id="d" tabIndex="4" /> </mx:GridItem> </mx:GridRow> </mx:Grid> <mx:Button label="Reset" click="reset(event)"/> </mx:VBox>
Components also have a setFocus()
method. In the preceding example,
focusManager.setFocus(a)
could be
rewritten as a.setFocus()
to
achieve the same goal.
Components must be visible and enabled in order to receive focus.
If a component is not visible or if it's disabled, it's taken out of the
list of focus-enabled components for a FocusManager
, and even if you explicitly call
setFocus()
, you can't apply the focus
to that component until it's both enabled and visible.
You can listen for keyboard events like you listen for any other
sort of event using inline MXML attributes and/or ActionScript. All
display objects, including all controls, containers, and the stage
itself, dispatch events of type flash.events.KeyboardEvent
when the user
presses a key on the keyboard. There are two distinct events with each
key press: keyUp
and keyDown
, which are represented by the KeyboardEvent.KEY_UP
and
KeyboardEvent.KEY_DOWN
constants.
The KeyboardEvent
type
defines several properties specific to the event. Among
those properties are the keyCode
property, which contains the code of the key that was pressed, and the
charCode
property, which contains the
code of the specific character. The keyCode
and charCode
properties are the same for all
alphanumeric keys when they are not Shifted, Ctrl’d, or Alt’d. For
alphanumeric keys the key and character codes are the ASCII
codes.
The flash.ui.Keyboard
class
defines constants that you can use for comparisons with key codes for
non-alphanumeric keys. For example, Keyboard.ENTER
and Keyboard.SHIFT
contain the key code
values for the Enter and Shift keys.
Components dispatch keyboard events only when they have focus. Example 10-17 uses this fact to create a simple context-based help system for a form in an application.
Example 10-17. A simple keyboard event example
<?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ import mx.controls.TextArea; import mx.managers.PopUpManager; import mx.containers.TitleWindow; import mx.events.CloseEvent; private var _helpWindow:TitleWindow; private function formKeyUpHandler(event:KeyboardEvent):void { if(_helpWindow != null) { return; } if(event.keyCode == Keyboard.F1) { _helpWindow = TitleWindow(PopUpManager.createPopUp(this, TitleWindow, true)); _helpWindow.title = "Application Help"; _helpWindow.width = 400; _helpWindow.height = 400; _helpWindow.showCloseButton = true; _helpWindow.addEventListener(CloseEvent.CLOSE, closeHandler); var textArea:TextArea = new TextArea(); textArea.percentWidth = 100; textArea.percentHeight = 100; _helpWindow.addChild(textArea); if(event.currentTarget == firstName) { textArea.text = "Specify your first name, e.g. Bob"; } else if(event.currentTarget == lastName) { textArea.text = "Specify your last name, e.g. Smith"; } else if(event.currentTarget == email) { textArea.text = "Specify your email address, e.g. [email protected]"; } else if(event.currentTarget == accountType) { textArea.text = "Select an account type from the drop-down"; } else { textArea.text = "Generic application help"; } } } private function closeHandler(event:CloseEvent):void { PopUpManager.removePopUp(_helpWindow); _helpWindow = null; } ]]> </mx:Script> <mx:Form> <mx:FormItem label="First Name"> <mx:TextInput id="firstName" keyUp="formKeyUpHandler(event)" /> </mx:FormItem> <mx:FormItem label="Last Name"> <mx:TextInput id="lastName" keyUp="formKeyUpHandler(event)" /> </mx:FormItem> <mx:FormItem label="Email"> <mx:TextInput id="email" keyUp="formKeyUpHandler(event)" /> </mx:FormItem> <mx:FormItem label="Account Type"> <mx:ComboBox id="accountType" dataProvider="[bronze,silver,gold,platinum]" keyUp="formKeyUpHandler(event)" ></mx:ComboBox> </mx:FormItem> </mx:Form> </mx:Application>
In Example 10-17, the user can press F1, and a help window appears with help specific to the control that has focus.
The KeyboardEvent
class also
defines the Boolean properties ctrlKey
, altKey
, and shiftKey
, which tell you whether the user is pressing the Ctrl key,
the Alt key, or the Shift key. The following rewrite of the if
statement in Example 10-17 causes the help to appear
only when the user presses Ctrl-F1:
if(event.keyCode == Keyboard.F1 && event.ctrlKey
) {
If you want to listen to events globally within an application, add listeners to the application container:
this.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);