Focus Management and Keyboard Control

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.

Controlling Tab Order

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>

Note

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.

Programmatically Controlling Focus

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>

Note

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.

Responding to Keyboard Events

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);
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset