Use the Spacebar or click on the Emenoh logo to access the global navigation menu
Go Back

Using DispatchEvent to provide keyboard input support in Flash AS3

One technique for providing keyboard input support in Flash is to emulate mouse events using the DispatchEvent method.

This technique allows you to use the arrow keys (or any other key) rather than relying on a tab based navigation scheme. The advantage here is that you can provide multiple directions of navigation. A tab based navigation scheme only allows for forward and back, while arrow keys give you two axis of navigation.

The other advantage to this method is that you can retrofit an existing application which already has Mouse Events defined. Alternatively it allows you to set up both mouse and keyboard support with one set of functions.

Download the example code.


package
{
	//these classes are standard setup for a typical flash movie
	import flash.display.*;
	import flash.events.*;
	import flash.text.*
	
	//these classes provide standard tweening support - you all know Tweener ;-p
	import caurina.transitions.Tweener;
	import caurina.transitions.Equations;
	//this class provides event management via a dictionairy
	import com.k2xl.util.EventManager;
	
	//these are the special sample classes for this example of keyboard input
	import com.emenoh.utilities.keyboard.KeyHandler;
	import com.emenoh.utilities.keyboard.KeyType;
	
	public class KeyBoardInputExample extends Sprite
	{
		
		private var myTextField:TextField;//just some sample text to describe the menu
		private var myTextFormat:TextFormat;
		private var leftNav:Array;
		private var subMenuMC:MovieClip;
		//keyboard input vars
		private var _shiftOn:Boolean;
		private var _capsOn:Boolean;
		private var KH:KeyHandler;
		private var k:String;
		private var totalItems:Number;
		private var itemIndex:Number;
		private var currentItem:Object;
		private var nextItem:Object;
		private var prevItem:Object;
		private var currentItemOption:Object;
		private var nextItemOption:Object;
		private var prevItemOption:Object;
		private var currentItemParent:Object;
		private var currentItemGroup:String = "leftNav";
		
		
		public function KeyBoardInputExample () 
		{
			KH = new KeyHandler();
			//Here we add an event to discover when this is added to the stage, so we can do stage related things
			//the EventManager class will let us keep track of all events without needing to add a typical "Dispose" method
			EventManager.addEventListener(this,Event.ADDED_TO_STAGE, onAdded);
			//This is also an example of ensuring that the stage is available before we do anything...
			
			
		}
		
		
		
		private function onAdded(e:Event){
			//trace("added");
                        EventManager.addEventListener(stage, KeyboardEvent.KEY_UP, navUp);
                        EventManager.addEventListener(stage, KeyboardEvent.KEY_DOWN, navDown);

			drawScreen(); // I could call this init or something else generic, I prefer a descriptive name

			EventManager.removeAllListeners(null,Event.ADDED_TO_STAGE);//removes all added to stage listeners
		}
		
		
		//This is where the magic happens
		private function navUp(e:KeyboardEvent){
			//trace(e.keyCode);
			k = KH.convertKeyCodeToKey(e.keyCode, e.shiftKey)
			
						
			switch(k)
				{
					case KeyType.LEFT://left arrow
					        trace(currentItem.name);
						if(currentItemGroup == "subNav"){
							currentItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OUT, true, false));
							currentItem = currentItemParent;
							currentItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER, true, false));
							totalItems = leftNav.length;
							currentItemGroup = "leftNav";
						}
						break;
					case KeyType.RIGHT://right arrow
					        trace(currentItem.name);
						if(currentItemGroup == "leftNav"){
							currentItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OUT, true, false));
							currentItem.dispatchEvent(new MouseEvent(MouseEvent.CLICK, true, false));
							currentItemGroup = "subNav";
							currentItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER, true, false));
						}
						break;
					case KeyType.UP://up arrow
					        trace(currentItem.name);
						if(currentItemGroup == "subNav"){
						currentItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OUT, true, false));
						prevItem = getPrevSubItem(currentItem);
						prevItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER, true, false));
						currentItem = prevItem;
						}else{
						currentItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OUT, true, false));
						prevItem = getPrevItem(currentItem);
						prevItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER, true, false));
						currentItem = prevItem;
						}
						break;
					case KeyType.DOWN://down arrow
					        trace(currentItem.name);
						if(currentItemGroup == "subNav"){
						currentItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OUT, true, false));
						nextItem = getNextSubItem(currentItem);
						nextItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER, true, false));
						currentItem = nextItem;
						}else{
						currentItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OUT, true, false));
						nextItem = getNextItem(currentItem);
						nextItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER, true, false));
						currentItem = nextItem;
						}
						break;
					case KeyType.OK://select current item
					        trace(currentItem.name);
						if(currentItemGroup == "leftNav"){
							currentItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OUT, true, false));
							currentItem.dispatchEvent(new MouseEvent(MouseEvent.CLICK, true, false));
							currentItemGroup = "subNav";
							currentItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER, true, false));
						}else{
							currentItem.dispatchEvent(new MouseEvent(MouseEvent.CLICK, true, false));
						}
						break;
				}
				
		}
					
		
		
		private function navDown(e:KeyboardEvent){
			
			//if we needed an event to happen on key down we could put the code here
						
				
		}
		
		
		
		
		private function drawScreen(){
			
			//this uses my helper function at the bottom of this class... normally I would put this in a super class and extend from it
			myTextFormat = createTextFormat("Verdana", 0xFFFFFF, 18, "left", false);
			myTextField = createTextField(20,300,400,400);
			myTextField.multiline = true;
			myTextField.wordWrap = true;
			myTextField.width = 400;
			myTextField.text = "Use the arrow keys and/or Enter button to navigate as if you were using a remote control.";
			myTextField.setTextFormat(myTextFormat);
			addChild(myTextField);
						
			
			leftNav = new Array("PLANES", "TRAINS", "AUTOS");
			
			var currentItemSet = false;
			totalItems = leftNav.length;
			
			for(var i = 0; i < totalItems; i++){
				
				var leftNavBtn = new SelectButton();
				leftNavBtn.setTitle(leftNav[i]);
				leftNavBtn.x = 20;
				leftNavBtn.y = 20+(i*80);
				leftNavBtn.buttonWidth = 250;
				leftNavBtn.name = "leftNavBtn_"+i;
				leftNavBtn.extra.index = i;
				leftNavBtn.extra.type = leftNav[i];
				leftNavBtn.setClickEvent(leftNavClick);
				
				if(currentItemSet != true){
				currentItem = leftNavBtn;
				//trace(currentItem.name);
				currentItemSet = true;
				}
				
				
				addChild(leftNavBtn);	
				
				
			}
			
			
			currentItem.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER, true, false));
			//currentItem.dispatchEvent(new MouseEvent(MouseEvent.CLICK, true, false));
			
		}
		
		
		
		
		
		private function leftNavClick(e:MouseEvent){
			
			loadSubNav(e.target.extra.type);
			
			for(var i=0; i < leftNav.length;i++)
			{
				var selectButton = getChildByName("leftNavBtn_"+i);
					selectButton.unSelectButton();
			}
			e.target.selectButton();
			
			currentItemParent = e.target;
			currentItemGroup = "subNav";
			
			
		}
		
		
		private function loadSubNav(_type){
			if(subMenuMC != null){
				removeChild(subMenuMC);
			}
			var subNavItems;
			switch(_type) {
				case "PLANES":
				subNavItems = new Array("ONE-A", "TWO-A", "THREE-A");
				break;
				case "TRAINS":
				subNavItems = new Array("ONE-B", "TWO-B", "THREE-B");
				break;
				case "AUTOS":
				subNavItems = new Array("ONE-C", "TWO-C", "THREE-C");
				break;
				
			}
			
			subMenuMC = new MovieClip();
			subMenuMC.x = 300;
			subMenuMC.y = 20;
			
			var currentItemSet = false;
			totalItems = subNavItems.length;
			
			for (var i = 0; i < totalItems; i++){
				
			
			
			var subNavButton:MovieClip = new MenuButton();
			//selectButton2.name = 'selectButton1';
			
			subNavButton.setTitle(subNavItems[i]);
			if (subNavButton.btn_label.length > 50){
				subNavButton.btn_label.replaceText(50, 2000, "...")
			}
			subNavButton.setClickEvent(dummyEvent);
			subNavButton.x = 0;
			subNavButton.y = 50*i;
			subNavButton.buttonWidth = 200;
			subNavButton.name = "subNavButton_"+i;
			//subNavButton.extra.path = filePath;
			subNavButton.extra.index = i;
			//trace(navButton.name)
			if(currentItemSet != true){
				currentItem = subNavButton;
				currentItemSet = true;
				}
				

				
			subMenuMC.addChild(subNavButton);
				
				
			}
			
			addChild(subMenuMC);
			
		}
		
		private function dummyEvent(e:MouseEvent){
			
			trace("Yeah we clicked a subnav item");
		}
		
		private function getNextItem(_currentItem){
			var index = Number(_currentItem.extra.index);
			var parentMC = _currentItem.parent;
			var result;
			
			if(index < Number(totalItems-1)){
			result = parentMC.getChildByName("leftNavBtn_"+Number(index+1));
			}else{
			result = parentMC.getChildByName("leftNavBtn_0");
			}
			return result;
			
			}
			
		private function getPrevItem(_currentItem){
			var index = Number(_currentItem.extra.index);
			var parentMC = _currentItem.parent;
			trace(totalItems);
			var result;
			if(index > 0){
			result = parentMC.getChildByName("leftNavBtn_"+Number(index-1));
			}else{
			result = parentMC.getChildByName("leftNavBtn_"+Number(totalItems-1));
			}
			
			return result;
			
			}
			
			
		private function getNextSubItem(_currentItem){
			var index = Number(_currentItem.extra.index);
			var parentMC = _currentItem.parent;
			var result;
			
			if(index < Number(totalItems-1)){
			result = parentMC.getChildByName("subNavButton_"+Number(index+1));
			}else{
			result = parentMC.getChildByName("subNavButton_0");
			}
			return result;
			
			}
			
		private function getPrevSubItem(_currentItem){
			var index = Number(_currentItem.extra.index);
			var parentMC = _currentItem.parent;
			trace(totalItems);
			var result;
			if(index > 0){
			result = parentMC.getChildByName("subNavButton_"+Number(index-1));
			}else{
			result = parentMC.getChildByName("subNavButton_"+Number(totalItems-1));
			}
			
			return result;
			
			}
		
		
		public function createTextField(x:Number, y:Number, width:Number, height:Number):TextField 
		{
				var result:TextField = new TextField();
				result.x = x; 
				result.y = y;
				result.autoSize = TextFieldAutoSize.LEFT;
				result.embedFonts = true;
				result.selectable = false; 
				result.width = width; 
				result.height = height;
				return result;
       			}
				
		public function createTextFormat(font:String, color, size:Number, align:String, bold:Boolean):TextFormat 
		{
				var result:TextFormat = new TextFormat();
				result.font = font; 
				result.color = color;
				result.size = size;
				result.align = align;
				result.kerning = true; 
				result.bold = bold;
				return result;
       			}
		
	}
	
	
}