How to prevent a component from losing focus on tab press

You have a flex component and you want to prevent the user from being able to leave its focus using the tab key.

Here's what I tried first without any success.

  • Cancel the TAB key down event.
  • Change or extend the focus manager's behaviour.
  • Cancel the focus out event. (This almost worked, except when the focus out causes Flex itself to lose focus.)

If you know how to accomplish this with any of these approaches, I'd love to hear how.

So here's what I ended up doing.
I first created a dummy TabStopper UIComponent.

package  {
	import flash.events.FocusEvent;
 
	import mx.core.UIComponent;
	import mx.managers.IFocusManagerComponent;
 
	public class TabStopper extends UIComponent implements IFocusManagerComponent {
		public function TabStopper(isStart:Boolean) {
			super();
			tabIndex = (isStart) ? 1 : int.MAX_VALUE;
		}
		/**
		 * Immediately after a focus in event happens, the previous component is given the focus instead.
		 */
		override protected function focusInHandler(event:FocusEvent):void {
			if (enabled) {
				var previousComponent:IFocusManagerComponent = focusManager.getNextFocusManagerComponent(!event.shiftKey);
				previousComponent.setFocus();
			} else {
				var nextComponent:IFocusManagerComponent = focusManager.getNextFocusManagerComponent(event.shiftKey);
				nextComponent.setFocus();
			}
		}
	}
}

Then, in the component you want to prevent tabbing, you just say:

  addChild(new TabStopper(true));
  addChild(new TabStopper(false));

So essentially, what this does, is it makes it so that if the user is using Tab, there are two end pieces on your component that force the focus back.

If you are using SDK 3.2 or later, there will be a slight change.
Replace the focusInHandler with the following:

// sdk 3.2 and later
override protected function focusInHandler(event:FocusEvent):void {
	if (enabled) {
		var direction:String = (event.shiftKey) ? FocusRequestDirection.FORWARD : mx.events.FocusRequestDirection.BACKWARD;
		focusManager.moveFocus(direction);
	} else {
		var direction:String = (!event.shiftKey) ? FocusRequestDirection.FORWARD : mx.events.FocusRequestDirection.BACKWARD;
		focusManager.moveFocus(direction);
	}
}

It does the same thing, but the Flex API changed their focusManager a bit.

This works for me, at least within a TextArea as to insert the Tab character instead of losing focus, but I don't see why this wouldn't work for other controls. I capture the keyDown event with the following handler from a TextArea whose id is taCode:

private function keyPressed( e:KeyboardEvent):void {
if (e.ctrlKey || e.shiftKey) return;
if (e.charCode == 9) {
taCode.text = taCode.text.substr(0, taCode.selectionBeginIndex)+"\t"+
taCode.text.substr( taCode.selectionBeginIndex);
taCode.setSelection( taCode.selectionBeginIndex+1,taCode.selectionBeginIndex+1);

this.setFocus();
}
}

Actually I add a behavior should the user press Tab (charCode == 9), do my stuff (inserting the "\t") and
call this.setFocus(). The method setFocus() doesn't actually reclaim the focus (since that would have no effect, because later on the keyDown event would change it to the next component), but pushes a "change focus" event in the event dispatcher queue, after the current one.

As I understands, my handler captures the keyDown event when traveling down the components hyerarchy, so the component that handles the focus changes already processed it and added it own "change focus" event in the event queue, so my "change focus" request (to get it back) goes afterwards. When the keyDown event finishes, the event dispatcher picks the next even from the queue that actually changes the focus to the next component, but after that my "change focus" event is processed and the focus comes back to my TextArea. This is unnoticeable for the user, because just after the events queue is empy the presentation is invalidated, so the last one that got focus is the only one being rendered as such.

I know, it looks a silly solution, and it took me time to figure it out. One trends to think the setFocus() is executed inmediatelly as it is being called (you put it in the code where you want it to happen... which seems to be before the current even finishes). As for me, part of this assumption comes from expecting the Flash Player virtual machine to be multi-threaded (after all , we see effects run "in parallel") but it isn't. It is the event dispatch queue the one that provides this parellel processing illusion, Flex/Flash components heavily relies on this, and it's because of this that Flex documentation suggests you to use it as well: the order of execution or the code spread among several controls is dictated by the event queue and not by the order of methods calling in every component code, and without knowing which metods dispatches events to do a task, and which ones actually does the task (that it's what we often expect), when we read (and write) the code we're often misslead about the execution order.

It seems this solution does not work if the editor which I want to prevent loosing focus is an itemEditor in a DataGrid. Somehow the DataGrid captures and handles the TAB key before it reaches the editor.
My original problem was: I wanted to conditionally prevent an editor being left if its contents are invalid. Do you know about a solution for this in DataGrids?

Thanks
Mischa

I do not know. I actually posted this because I was using it for the ElfGrid. Which is a replacement datagrid.