[Flex]データグリッド内のアイテムレンダラーでTABキーを押下してフォーカスを移動する

データグリッドのアイテムレンダラーを使用していて、コンテナを使用しているカスタムコンポーネントでも、TABキーを押下して順々に各コントロールにフォーカスが移動できれば、慣れたユーザーなら操作が便利になるかと思います。

例えば、VBoxコンテナに TextInput を2つ並べたカスタムコンポーネントのアイテムレンダラーの場合、TABキーを押下したときに下記のような感じでフォーカスが移動するイメージです。

tab-itemrenderer.jpg 画像1.TABキー押下時のフォーカス移動のイメージ

もっとスマートな方法もあるかと思いますが、とりあえずアイテムレンダラーに使うカスタムコンポーネントに以下のカスタマイズを加えることで自分がやりたいことを実現しました。

※今回の例では、フォーカス移動に焦点をあてているので、データを表示するアイテムレンダラーのカスタムコンポーネントとしては不完全ですのでご注意ください。

MXML

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"	xmlns:local="*">
	<mx:Script>
		<![CDATA[
			[Bindable]
			private var dgData:Array = [
			{no:1, name:'氏名1', age:21, address:'住所1'},
			{no:2, name:'氏名2', age:22, address:'住所2'},
			{no:3, name:'氏名3', age:23, address:'住所3'},
			{no:4, name:'氏名4', age:24, address:'住所4'},
			{no:5, name:'氏名5', age:25, address:'住所5'}
			];
		]]>
	</mx:Script>
	<mx:DataGrid dataProvider="{dgData}" editable="true">
		<mx:columns>
			<mx:DataGridColumn headerText="列1" dataField="no" editable="false"/>
			<mx:DataGridColumn headerText="列2" rendererIsEditor="true" editorDataField="">
				<mx:itemRenderer>
					<mx:Component>
						<local:TabMove verticalScrollPolicy="off" horizontalScrollPolicy="off"/>
					</mx:Component>
				</mx:itemRenderer>
			</mx:DataGridColumn>
			<mx:DataGridColumn headerText="列3" rendererIsEditor="true">
				<mx:itemRenderer>
					<mx:Component>
						<local:TabMove verticalScrollPolicy="off" horizontalScrollPolicy="off"/>
					</mx:Component>
				</mx:itemRenderer>
			</mx:DataGridColumn>
		</mx:columns>
	</mx:DataGrid>
</mx:Application>

このMXMLは、下記のカスタムコンポーネントを実装しているだけです。注意点は↓の記事と同じになりますので参照してください。

[Flex]データグリッド内の単純なアイテムレンダラーでTABキーを押下してフォーカスを移動する

 

カスタムコンポーネント

package
{
	import flash.accessibility.AccessibilityProperties;
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.LoaderInfo;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.FocusEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.geom.Transform;
	import flash.ui.Keyboard;
	
	import mx.containers.VBox;
	import mx.controls.TextInput;
	import mx.controls.listClasses.BaseListData;
	import mx.controls.listClasses.IDropInListItemRenderer;
	import mx.controls.listClasses.IListItemRenderer;
	import mx.managers.IFocusManager;
	import mx.managers.IFocusManagerComponent;
	import mx.managers.ISystemManager;

	public class TabMove extends VBox
	implements IListItemRenderer, IDropInListItemRenderer, IFocusManagerComponent
	{
		public function TabMove()
		{
			super();
		}
		
		/** テキストインプット1 */
		private var text1:TextInput = null;
		/** テキストインプット2 */
		private var text2:TextInput = null;

		public var text:String = "";

		//----------------------------------
		//  listData
		//----------------------------------
		
		private var _listData:BaseListData;

		[Bindable("dataChange")]
		[Inspectable(environment="none")]
		public function get listData():BaseListData
		{
			return this._listData;
		}
		
		public function set listData(value:BaseListData):void
		{
			this._listData = value;
		}
		
		override protected function createChildren():void
		{
			super.createChildren();
			
			this.text1 = new TextInput();
			this.text1.addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler);
			addChild(this.text1);
			
			this.text2 = new TextInput();
			addChild(this.text2);
		}
		
		private function keyFocusChangeHandler(event:FocusEvent):void
		{
			if (event.keyCode == Keyboard.TAB &&
				!event.isDefaultPrevented())
			{
				event.preventDefault();
				
				if (event.currentTarget == this.text1)
				{
					var fm:IFocusManager = this.focusManager;
					fm.setFocus(IFocusManagerComponent(this.text2));
				}
			}
		}

		override public function setFocus():void
		{
			var fm:IFocusManager = this.focusManager;
			if (IFocusManagerComponent(this.text1) != fm.getFocus() &&
				IFocusManagerComponent(this.text2) != fm.getFocus())
				fm.setFocus(IFocusManagerComponent(this.text1));
		}
	}
}

上記の例ではコンテナ内のコンポーネントは2つですが、ソースを工夫することで3つでも4つでも問題は無いと思います。

TABキー押下時にカスタムコンポーネントでフォーカス移動を実現させるために、カスタムコンテナに IFocusManagerComponent インタフェースを実装します。
これによりDataGrid コンポーネントで、アイテムレンダラー間のフォーカスの移動をやってくれます。

次に、コンテナ内の子コンポーネント作成時に、TABキー押下時に次のフォーカスをあてるコンポーネントが、同じカスタムコンポーネント内の場合は、KEY_FOCUS_CHANGE イベントを追加しておきます。

今回の例では、上段の TextInput にイベントを追加しています。

this.text1.addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler);


そして、そのイベントリスナーメソッドで、TABキーを押されたときはイベントをキャンセルし、次にフォーカスを移動させたいコンポーネントに対してフォーカス移動を行います。

				event.preventDefault();
				
				if (event.currentTarget == this.text1)
				{
					var fm:IFocusManager = this.focusManager;
					fm.setFocus(IFocusManagerComponent(this.text2));
				}

こうしないと、TABキーを押下したときに、カスタムコンポーネント内ではなく、DataGrid のカスタムコンポーネント間でフォーカスが移動してしまいます。

tab-itemrenderer2.jpg

画像1.カスタムコンポーネント間でフォーカスが移動してしまう例

次に、SetFocus メソッドをオーバーライドします。

		override public function setFocus():void
		{
			var fm:IFocusManager = this.focusManager;
			if (IFocusManagerComponent(this.text1) != fm.getFocus() &&
				IFocusManagerComponent(this.text2) != fm.getFocus())
				fm.setFocus(IFocusManagerComponent(this.text1));
		}

DataGrid コンポーネントは、TABキーなどでフォーカスを移動するときはアイテムエディターコンポーネント自体にフォーカスを移動します。
今回の例では VBox コンテナにフォーカスが移動してしまうわけです。
使う側からすれば、VBox コンテナにフォーカスが移動しても困るので、ここの処理で、フォーカスがコンテナの子コンポーネント以外のときは、一番最初にフォーカスを当てたいコンポーネントにフォーカスを移動させています。

ここまでで、大体やりたいことが出来るようになりました。

ただし、アイテムエディターでは、Enterキーを押したときは横方向ではなく下方向にフォーカスが移動していきます。
今回の方法では、Enterキーについてはカスタムコンポーネント間でフォーカスが移動してしまいます。
業務での優先順位が低いこともあり、そこはただいま調査中です。

このブログについて

このブログは吉祥寺にあるブレインチャイルド株式会社の社員で投稿しています。
業務ではまってしまったことや発見したこと。
自分達で新たに学習してみようと思って勉強し始めたことなどを綴っています。
こんな社員が働いているブレインチャイルドに興味がわいててきたなら、是非お問い合わせください。
会社案内
求人案内
先輩のコメント