Flex の DataGrid で、列の定義をするのに DataGridColumn を使いますが、それを、2つの DataGrid で同じ DataGridColumn を共有し、それぞれの DataGrid の列定義を同期させてみようという小ネタをご紹介します。
例として、上下に並べた DataGrid で、どちらかの列幅を変えたときにもう片方の列幅も一緒に変更されるといったことをやってみました。

以下がサンプルソースになります。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*"
	creationComplete="creationCompleteHandler(event)">
	<mx:Script>
		<![CDATA[
			import mx.events.DataGridEvent;
			import mx.events.FlexEvent;
			
			private var dgColumns:Array = new Array();
			
			[Bindable]
			private var dgData1:Array = [
			{no:1, name:'氏名1-1', age:21, address:'住所1-1'},
			{no:2, name:'氏名1-2', age:22, address:'住所1-2'},
			{no:3, name:'氏名1-3', age:23, address:'住所1-3'},
			{no:4, name:'氏名1-4', age:24, address:'住所1-4'},
			{no:5, name:'氏名1-5', age:25, address:'住所1-5'}
			];
			[Bindable]
			private var dgData2:Array = [
			{no:1, name:'氏名2-1', age:31, address:'住所2-1'},
			{no:2, name:'氏名2-2', age:32, address:'住所2-2'},
			{no:3, name:'氏名2-3', age:33, address:'住所2-3'},
			{no:4, name:'氏名2-4', age:34, address:'住所2-4'},
			{no:5, name:'氏名2-5', age:35, address:'住所2-5'}
			];
			
			public function creationCompleteHandler(event:FlexEvent):void
			{
				dataGrid2.columns = dataGrid1.columns;
				
				dataGrid1.addEventListener(DataGridEvent.COLUMN_STRETCH, dgColumnStretch);
				dataGrid2.addEventListener(DataGridEvent.COLUMN_STRETCH, dgColumnStretch);
			}
			
			public function dgColumnStretch(event:DataGridEvent):void
			{
				if (event.currentTarget == dataGrid1)
					dataGrid2.invalidateList();
				else
					dataGrid1.invalidateList();
			}
		]]>
	</mx:Script>
	
	<mx:VBox>

	<mx:DataGrid id="dataGrid1" dataProvider="{dgData1}">
		<mx:columns>
			<mx:DataGridColumn headerText="NO" dataField="no"/>
			<mx:DataGridColumn headerText="氏名" dataField="name"/>
			<mx:DataGridColumn headerText="年齢" dataField="age"/>
			<mx:DataGridColumn headerText="住所" dataField="address"/>
		</mx:columns>
	</mx:DataGrid>
	
	<mx:DataGrid id="dataGrid2" dataProvider="{dgData2}">
	</mx:DataGrid>

	</mx:VBox>
</mx:Application>


上記ソースでは、2つ目の DataGrid の columns プロパティを定義せずに、アプリケーションの creationCompleteイベントのハンドラーで、dataGrid1 の columns プロパティをそのままコピーしています。

	dataGrid2.columns = dataGrid1.columns;

この時点で、上下の DataGrid の列定義が同じになり、上の DataGrid と 下の DataGrid の列定義(ヘッダー文字列、表示する内容など)が同じものになります。
このままですと、同じ DataGridColumn を使っているだけですので、初期表示の列定義をコピーしただけになります。

そこで、それぞれの DataGrid の columnStretch イベントのハンドラーメソッドで、お互いの invalidateList メソッドを呼んで描画をリフレッシュするようにします。

	private function dgColumnStretch(event:DataGridEvent):void
	{
		if (event.currentTarget == dataGrid1)
			dataGrid2.invalidateList();
		else
			dataGrid1.invalidateList();
	}

すると、上下の DataGrid の列幅が、常に同期するということができるようになります。
同じ DataGridColumn を見に行っているので当たり前といえば当たり前です。
ただ、列幅変更時にリフレッシュしない場合、表面上はそれぞれの DataGrid の列幅が違いますが、裏側では同じですので、再表示のタイミングでいきなり列幅が変わるという見方によっては不具合と捉えられる怪しい挙動をしてしまいます。

以下が、上記ソースで作成したFlexアプリケーションのサンプルになります。
上下それぞれの DataGrid の列幅を変更して、実際の動作を試してみてください。

最後に、DataGrid の columns プロパティは Arrayクラスですが、値を取得または設定するとき、元データの一連のエレメントを取り出して、新しい配列で取得または設定する sliceメソッドを使用しています。

そのため、列の順序を変更した場合は、上記のやり方だけですと上下の DataGrid で別々の列順序になってしまいます。

列の順序を変更した場合は、DataGrid の headerShift イベントのハンドラーメソッドなどで、columns の順序も考慮した同期を取るようにしたほうが良いと思います。

FlashBuilder4から簡単にアイテムレンダラーの設定を行う事が可能となりました。
今回はその方法の紹介として、データグリッドにカスタムレンダラー部品を埋め込んだサンプルを作成してみます。

最初に、メインアプリケーションを作成します。 サンプルでは、<s:Panel>内 に<mx:DataGrid>を配置しました。
メインアプリケーションのcreationCompleteハンドラでデータグリッドの表示データを設定します。 ji6-1.png


	
		
	

	
	
	
		
	

	
		
			
				
				
				
				
				
				
			
		
	

[実行結果]
ji6-2.png

上記データグリッドのチェック列にチェックボタンを表示し、チェックボタンが選択された場合、チェックボタンの横に「選択済」と文字列が表示されるようにアイテムレンダラーを設定しようと思います。

mxmlのデザインビューでデータグリッドを選択し、プロパティにて「列の構成」ボタンをクリックします。
ji6-3.png

ここでは、ヘッダーテキストの値や幅などのプロパティの設定をはじめ、詳細ビューでは詳細な設定をおこなう事が可能です。データバインディングの欄にアイテムレンダラーの設定欄がありますので、横の紫のボタンをクリックし「アイテムレンダラーを作成」をクリックします。
ji6-4.png

パッケージとコンポーネント名を入力し終了ボタンをクリックします。
ji6-5.png

終了ボタンをクリックすると「列の構成」設定画面に戻ってくるので、先ほど作成したコンポーネントがアイテムレンダラー欄に設定されている事を確認しOKボタンをクリックします。
ji6-6.png

設定が完了するとパッケージエクスプローラー上に、先ほどアイテムレンダラーで設定したコンポーネントが作成されている事が確認できると思います。更に先ほどの設定で自動的に<mx:DataGridColumn>にitemRendererが設定されます。

次にアイテムレンダラーの部品を編集します。
<s:CheckBox>を配置しクリックハンドラ内でチェックボックスが選択された場合、チェックボックスのlabelプロパティの値に”選択済”を設定します。
ji6-7.png
デザインビューでサクサク設定を行えるので、多彩な表現を簡単に設定できるのは非常に嬉しいです。
以下はコードになります。



	
		
	

	
	
		


以上で設定は完了です。実際に実行したイメージは以下の様になります。ji6-8.png

GUIで簡単にアイテムレンダラーの設定を行う事が可能となったのでFlexに慣れていない方もサクサク色々な事ができるようになったのではないでしょうか?

つい先日、FlashBuilder4が発売となりました。

今回は、FlashBuilder4にて簡単なFlexアプリケーションを作成し、新機能などを紹介したいと思います。

Flash Builder4は公式サイトより体験版が入手可能です。

http://www.adobe.com/jp/products/flashbuilder/?view=topnew

 

まずはじめに、Flexプロジェクトを作成します。


1.パッケージエクスプローラーで右クリックし「Flexプロジェクト」を選択しプロジェクトを作成します。

2. アプリケーションの種類は「Web(Adobe Flash Playerで実行)」を選択し終了ボタンをクリックします。

ji5-1.pngのサムネール画像のサムネール画像

作成されたプロジェクト内のmxmlを見てみると、<s:Application>のようにsparkコンポーネントベースでApplicationが作成されます。
従来(Flex3まで)はFlex標準コンポーネントは、ネームスペースmxで始まるHaloコンポーネントで構成されておりましたが、FlashBuilder4からネームスペースsで始まるSparkコンポーネントが追加されました。
Flex標準コンポーネントの<TextInput>や<Panel>などのおなじみの部品もHaloコンポーネントとSparkコンポーネントでそれぞれ存在します。
プロジェクト作成時に自動的に作成されるmxmlのソースを見てみると<fx:Declarations>など、従来になかった表記がされています。
これらmxml2009に関する詳細は、「Flex4マスターシリーズ」で詳しく説明されておりますのでそちらもご参照ください。

http://www.adobe.com/jp/devnet/flex/articles/flex4_tutorials.html

 

次にデザインビューにて、コンポーネントを配置し画面を作成します。
サンプルでは以下のログインフォームの様な画面を作成しました。

ji5-2.png

<?xml version="1.0" encoding="utf-8"?>

	
		
	
	
		
			
				
					
					
				
					
					
						
					
					
        		
			
		
	

ここから、デザインビューでデザインの詳細設定を行っていこうと思います。
まず、ログインフォーム内(s:Panel)に配置されたVGroupのパディングを設定します。
FlashBuilder4からは、デザインビューのプロパティからパディングの設定も行う事が可能となりました。
以下の通り、全て10で設定します。

ji5-3.png

次にログインボタンをクリックするとアラートを表示する機能を実装します。
<s:Button>にclick=""と入力するとコードアシストの候補に「Clickハンドラーを作成」と表示されます。

ji5-4.png

「Clickハンドラーを作成」を選択すると、mxml内に「部品id+イベントハンドラ」と命名されたイベントハンドラが自動生成されます。
これもFlashBuilder4から追加された機能となりますがなかなか便利です。

ji5-5.png

作成されたイベントハンドラにアラートを実装します。

サンプルでは以下の通り実装しました。

;


	
		
	


	
		
	
	
		
			
				
					
				
				
					
				
			
			
		
	

 

次に、今回から追加された、「Flexテーマの変更」を行ってみようかと思います。
Flexプロジェクトを右クリックし、「Flexテーマ」を選択します。

ji5-6.png

FlashBuilder4からは「Flexテーマ」の変更によって簡単に外観を変更する事が可能です。
デフォルトは「Adobeテーマ - Spark」が選択されているので、他のテーマを選択しOKボタンをクリックします。

[デフォルト:Spark]

ji5-7.png

[Arcade]

ji5-8.png

以上でサンプルは完成です。

今回は、ボタンをクリックするとアラートが表示されるだけという非常に簡単なサンプルでしたが、それだけのアプリケーションを作成するに当たって、色々な変更点を紹介する事が出来ました。
もちろんこれだけではなく非常に多くの変更点がありますので
早めに、FlashBuilder4をいじり倒してみる事をお勧めします。

データグリッドのアイテムレンダラーを使用していて、コンテナを使用しているカスタムコンポーネントでも、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キーについてはカスタムコンポーネント間でフォーカスが移動してしまいます。
業務での優先順位が低いこともあり、そこはただいま調査中です。

データグリッドでアイテムエディターを使用すると、TABキーを押下したときに次のコントロールに遷移してくれます。

しかし、アイテムレンダラーを使用すると、TABキーを押下しても次のコントロールに遷移してくれません。

どうすれば実現できるかを調べたところ、単純なアイテムレンダラーであれば、アイテムエディターのようにTABキーを押下したときに次のコントロールに遷移させる方法は、簡単なプロパティの設定だけで実現できることがわかりました。

以下、その方法を簡単にご紹介します。

①データグリッドコントロールの editable プロパティを trueにします。
<mx:DataGrid editable="true">

※このプロパティを true に設定すると、データグリッド内の各列に アイテムエディターが作成されるので、不要な列は DataGridColumnの editable プロパティを false に設定します。

②アイテムエディターのインスタンスが必須ですので、アイテムレンダラーとアイテムエディターが同じ場合は、DataGridColumnの rendererIsEditor プロパティを false に設定します。
    <mx:DataGridColumn headerText="列2" dataField="name"
        rendererIsEditor="true" itemRenderer="mx.controls.TextInput"/>

簡単なサンプルを下記に示します。

<?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" dataField="name"
				rendererIsEditor="true" itemRenderer="mx.controls.TextInput"/>
			<mx:DataGridColumn headerText="列3" dataField="address"
				rendererIsEditor="true" itemRenderer="mx.controls.TextInput"/>
		</mx:columns>
	</mx:DataGrid>
</mx:Application>

単純な アイテムレンダラー(コンテナが無いコンポーネント)であれば、これだけです。一見したら何もしていないようにも見えます(^^;)

この方法は、FXUGのフォーラム(⇒ここ)で見つけました。

しかし、コンテナがあるアイテムレンダラー(1つのアイテムレンダラー内に複数のコンポーネントがある)のように、まずアイテムレンダラー内だけでフォーカス遷移し、最後のコンポーネントでTABキーを押下されたときに、次のアイテムレンダラーに移るといったような複雑なことを実現するには、少々アイテムレンダラーに使用するコンポーネントをカスタマイズする必要があります。

それについては、次回ご紹介します。
 

<<前のページ 1234567891011

このブログについて

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