Archive for 6月, 2009
さて、TabNavigatorのタブ拡張は今回でとりあえずは終了にしようと思います。
まだまだ、「閉じる」場合と「閉じない」場合のタブを混在させたい!とか、いろいろあるのですが・・・
要望次第でその後は考えていこうと思います。
さて、前回、×バツボタンまでは表示できるようにしました。
この×バツボタンを押して、タブを閉じるところまでです。
前回のところまでできてれば、後はその画像にClickのイベントでできじゃないか?
と思った方。残念ながらできません・・・・
私もこれではまってしまいました。
これが理由になっているかわかりませんが、Tabって、Buttonの拡張なんですよね。
Clickのイベントがとれないのって、ClickするためにできているTabの上にClickできるイベントを取得できないということですかね。
とにかく、Clickでイベントを登録しても動かないのです。。。。
そこで、考えたのが、タブ全体に対するMouseDownのイベントです。
これを試すと、どうやらMouseDownのイベントはとれるようです。
では、MouseDownが押された時に、そのマウスの場所が×バツの画像の上の位置だったら、
×バツボタンが押されたことと同じである。ということです。
このメソッドをCustomTabクラスに実装していきます。
[AS]
override protected function createChildren():void{
// ・・・前回までの実装
this.addEventListener(MouseEvent.MOUSE_DOWN,_mouseDownHandler);
}
[/AS]
のようにまずはマウスが押された時のイベントを登録しましょう。
[AS]
private function _mouseDownHandler(e:MouseEvent):void{
if(e.localX > closeImage.x && e.localX < ( closeImage.x + closeImage.width)){
if(e.localY > closeImage.y && e.localY < ( closeImage.y + closeImage.height)){
// 実は以下の2行は十分説明できるほど、理解していません。
e.stopImmediatePropagation();
e.preventDefault();
var num:Number = parent.getChildIndex(this);
var closeEvent:ItemClickEvent = new ItemClickEvent(“clickTabClose”,true,true);
closeEvent.index = num;
dispatchEvent(closeEvent);
}
}
}
[/AS]
上の例のように、マウスの位置と、画像の位置で、画像の上の位置に来たときに、閉じた!イベントを発行します。
先ほども上のほうで書きましたが、TabはButtonクラスの継承ですので、Buttonが押されたことと勘違いされないように、
一応、それ以上イベントが伝播しないようにしております。
しかし、残念ながらまだ私にはここの部分をより深く検証したわけでなく、説明できるほど理解していないので、これが本当にどのように他に影響し得るのか?
などが説明ができませんのであしからず・・・・
さて、発行するイベントは何でもいいのですが、何番目のタブが押されただけは把握したいので、
[AS]
parent.getChildrenIndex(this);
[/AS]
で取得しています。0(ゼロ)から始まりますのでご注意を。
後は、これをどこでもいいので、取得して閉じるようにすれば問題ありません。
今回は、CustomTabNavigator内で取得し、閉じるようにしましょう。
[AS]
override protected function createChildren():void{
// ・・・今までの実装がだらだら
this.addEventListener(“clickTabClose”,clickTabCloseHandler);
}
private function clickTabCloseHandler(e:ItemClickEvent):void{
var child:DisplayObject = this.removeChildAt(e.index);
child = null;
}
[/AS]
ボタンが押されたら強制的に閉じるのであればこれでいいとも思いますが、
実際には変更されていたら?とか、POPUPを出してから?とか・・・
があると思いますので、そこは各自ご実装ください。
FlexにはArrayCollectionというArrayとほとんどおなじようなものがある。
しかし、ArrayCollectionには変更時のイベントがあるので、これを使ってちょっと便利なことができる。
(というより、私にとっては困った非同期処理への苦肉の策とでもいうのでしょうか?)
私がこの機能を使おうと思ったのは、pop3クライアントを作っているのだが
(出来上がったら公開しようと思っていますが、まだ、ちょっと文字化け関連でバグがありまして・・)
Socket通信は非同期処理になってしまうということでした。
つまり、2通のメールを取得しようとしても、受信しているデータがどちらのメールかの保障がないということになります。
2つのオブジェクトを作ってしまえば2通の場合にはそれでもいいのですが、1000通の場合なら、1000個のオブジェクト?
ということになるとちょっと抵抗があったので、1通目の取得が終わったら2通目を取りにいくということを実装しようと思い、
ここで、非同期処理を同期化(というのでしょうか?)させなくてはなりません。
探していると、そんなようなライブラリがあったのですが、ArrayCollectionの変更のイベント処理を使えば簡単にできそうだったので、
それで実装してしまいました。
要は簡単です。
pop3.readMail(1);
pop3.readMail(2);
のような感じメールを読むメソッドを実行したときにこの命令をArrayCollectionにためておきます。
[AS]
private function readMail(i:int):void{
var cmd:Object = new Object();
cmd.key = “RETR”;
cmd.value = i;
arraycollection.addItem(cmd);
}
[/AS]
のような感じで・・・・
それで、arrayCollectionには、
[AS]
arraycollection.addEventListener(CollectionEvent.COLLECTION_CHANGE,executeCmd);
[/AS]
のように、変更された時にexecuteCmdメソッドという感じで実行するようにしておくのです。
ただし、これを単純にやってしまっては非同期処理が連続で動いてしまうだけです。
そこで、
[AS]
private function executeCmd(ce:CollectionEvent):void{
if(arraycollection.length > 0 ){
arraycollection.removeEventListener(CollectionEvent.COLLECTION_CHANGE, executeCmd);
var cmd:Object = arraycollection.removeItemAt(0);
// 何らかの受け取ったcmdの処理をする
executePopCmd(cmd);
}
}
[/AS]
このようにしておけば、2回目のreadMailメソッドでもメールを読みに行かないということです。
しかし、さらにこのままでは、永久に2回目のreadMailが実行されないことになってしまいます。
(上の例では、executePopCmd でPOP3のプロトコルを処理するものとしてみてください)
Socketですので実際には、ソケットからデータを読むということが非同期処理として扱わなければいけないのですが、
POP3の特徴としてデータの最後には.(ドット)で終了するという特徴があるので、
その終了処理の部分でarrayCollectionからreadMail(2)で蓄積されたタスクを取りだし、また、実行するというわけです。
そして、これをぐるぐると繰り返し、arrayCollectionが空っぽになったら、
[AS]
arraycollection.addEventListener(CollectionEvent.COLLECTION_CHANGE,executeCmd);
[/AS]
のようにまた、変更時のイベントを登録しておけば、順次処理をしていかなければいけないが、非同期処理が邪魔してしまうものに対しても有効です。
もっと、もっと汎用的につくられたライブラリを探してきてもいいのでしょうが、実際にはある程度用途が定まってしまえば、
このような方法で回避するのもいいのかなと思っています。
さて、前回は空のクラスだけを用意しただけですが、今回はこのクラスを実装していきます。
まずは、Tabに×(バツ)ボタンを表示するところを実装しましょう。
対象となるクラスは、CustomTabクラスです。
とりあえず、バツを表示する画像を用意してください。
私は自分で
のような画像を作成しました。
もし面倒ならこれを使ってください。サイズは 24×16です。このサイズは後でソースで記述します。
さて、まずはこのボタンをタブに表示します。
[AS]
package com.coltware.cise.ui {
import mx.controls.tabBarClasses.Tab;
import mx.controls.Image;
public class CustomTab extends Tab{
[Embed(source="/com/coltware/cise/ui/assets/close.png")]
private var tabCloseClass:Class;
private var closeImage:Image;
// ボタンの画像を指定する
private var btnWidth:int = 24;
private var btnHeight:int = 16;
public function CustomTab() {
super();
}
override protected function createChildren():void{
super.createChildren();
closeImage = new Image();
closeImage.visible = true;
closeImage.buttonMode = true;
closeImage.width = btnWidth;
closeImage.height = btnHeight;
var imageObj:Object = new tabCloseClass();
closeImage.source = imageObj;
addChild(closeImage);
}
}
}
[/AS]
これで表示はできます。ただし、思った場所に表示されないのです。

こんな感じに、おかしな場所に表示されてしまいます。
ここで、これを調整するメソッドをここに実装していきましょう。
まずは、ソースです。
[AS]
/**
* タブのサイズを大きくする
*
*/
override protected function measure():void{
super.measure();
measuredMinWidth = measuredMinWidth + btnWidth + 6;
measuredWidth = measuredWidth + btnWidth + 6;
}
/**
* 再表示処理
*/
override protected function updateDisplayList(nscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
this.updateCloseButton();
this.textField.x-=10;
}
/**
* 閉じるボタンを再表示する
*/
private function updateCloseButton():void{
closeImage.visible = true;
setChildIndex(closeImage, numChildren – 1);
closeImage.move(width – btnWidth – 6, this.textField.y + 1);
}
[/AS]
初めの、measure()は、サイズを決定するメソッドです。
これでバツボタンを表示するためのエリアを確保するため、横に大きくしましょう。
+6って書いたのは、実際やってみての私の気分です。このあたりは適当に調整してみてください。
次に、updateDisplayListです。この処理は画面更新時に呼ばれるメソッドですが、ここでボタンの位置をずらします。
updateCloseButton()の中でmoveしているところが実際、位置ずらしです。
ただ、バツ画像をずらしても、テキスト(ラベル)にかかってしまう可能性があるので、こちらもずらします。
さらにアイコンを表示している場合にはアイコンも調整が必要になるでしょう。このあたりは、ぜひ試してみてください。
次回は、このバツを押せるようにしていきます。
今回はComboBoxの表示についてです。ComboBoxとはHTMLでいう、Selectですね。
今回は、そのComboBoxの
とう感じのものを出したいなと思います。
ただし、これをdataProviderとして提供するのではなく、別なオプションとして提供したいのです。
私は面倒なので今までdataProviderとして記述してしまいましたが、本来はデータとしてではなくプレゼンテーションのみの部分なので、
やっぱり別なこととして扱いたいなーと思っていました。
そこで、本格的(?)に調査しましたところ、見つかりました。
結論からいえば promptというプロパティがComboBoxにはありました。
と書けばおしまい。・・・・・ だったはずなのですが、出ません。
なぜ・・・なぜ・・・
でも、ほかのプログラムで書いた時には出たのに・・・・・
いろいろ調べていたのですがどうしてもわかりません。
でも、回避する方法だけはわかりました。
<mx:ComboBox prompt=”—選択—” selectedIndex=”-1″>
のようにselectedIndex=”-1″も合わせて設定すれば大丈夫なようです。
なぜ、-1を設定するようにするかといえば、ComboBoxのset selectedIndexメソッドにマイナスならば・・・・promptを
って感じの部分があったので、まあ、関係があるのかなと入れてみました。
そしたら、ほぼ問題なく動くようになったので・・・というレベルです。
ただ、ソースでの問題ではなく、HTMLに埋め込んだswfの表示では問題が発生しましたが、airや、Flash Player単体での表示では問題がでませんでした。
引き続き、あたまの隅に置いておこうと思います。
PHPでWebアプリケーションを作るときに私はよく、PEARのHTML_Template_Sigmaを使う。
Smartyなども試したが、やっぱりこのテンプレートが一番使いやすい。
というのも、このテンプレートを使うとほかのテンプレートとちょっと違った便利な使い方ができるからだ。
なので、ほかの人にももっと使ってほしいと思い紹介します。
文法などはHTML_Template_Sigmaのサイトを見ればわかると思いますので、
私が便利だなとおもう点や、使い方を紹介していきたいとおもいます。
1. HTMLに限りなく近いのでHTMLエディタなどを使っても大丈夫。
これ、結構重要です。たいていのテンプレートはifや、forなどの特別な記述方法がありますが、このテンプレートはありません。
その代わり、ifや、forはプログラム側で操作しなけれないけません。これは、テンプレートをプログラマが触らない場合には大変重要です。
結局、特別な文法があるとせっかくHTMLのテンプレートを使ってもプログラマがそれを修正することになり、だったら、素のphpで十分だったりします。
ちなみに、HTML_Template_Sigmaでは、ifやforを使いたい場合には<!- BEGIN BLOCK –>xxxxxx<!– END BLOCK –>のように書き、
このブロック単位でそれを表示するのか?もしくは繰り返して表示するのかなどを制御します。
したがって、テンプレート側ではifでもforでも書き方は関係ありません。これはHTMLの記述者とプログラマが分かれているという前提では結構便利です。
勝手に変更されませんし。
2.変数に$がない!
こんなつまらないことですが、$がないということは非常に重要です。 すべての変数に$がつくのであれいいのですが、たいていは違います。
たとえばsmartyの文法ですが、
{foreach from=$data item=”item” key=”key” name=”loop”}
{assign var=”ret” value=”$val” }
など、$があったり、なかったり。意味が違うので当然ですが。
プログラマに理解せよというのは問題ありませんが、デザイナにこれを理解せよというのはちょっと無理があります。
その点、HTML_Template_Sigmaは{var}という感じで{と}でくくれば、そこが置き換わる文字です。
まあ、ここまでHTML_Template_Sigmaは要するに貧弱なので素晴らしいという書き方になってしまっていると思いますが、
まさに、HTMLテンプレートの目的がHTMLとプログラムの完全な分離を目指しているわけなのですが、現実は完全はありません。
あとは、分離の比率がどの程度にするか?ということですが、HTML_Template_Sigmaはかなり高いレベルで分離ができると思います。
その代わり、プログラムのほうにその処理が回ってしまうのですが・・・・
でも、プログラマにもHTML_Template_Sigmaならではの便利なこともあるのです。
今後、そのtipsなどを紹介したいと思います。
mx:Treeを使ってアイテムごとに違うアイコンを表示させるようにしたい。
この場合にいろいろな方法があるみたいだが、やっぱり一番いいのは iconFunctionを使う方法だろう。
どうやら、iconFieldというプロパティにデータの変数(属性)名を指定すればそれでもiconは変更できるみたいであるが、
クラス名を書かなければいけない。
なので、私がお勧めなのは、iconFunctionとCSSを使ってアイコンを指定する方法だ。
まずは下記のようにTreeオブジェクトを配置する。
<mx:Tree id="tree" labelField="@label" showRoot="true" width="200"
height="200"
iconFunction="{showIcon}"
styleName="treeIcons">
<mx:XMLListCollection id="Folder">
<mx:XMLList>
<folder label="Grp1">
<folder label="Grp1-1" icon="red"/>
<folder label="Grp1-2" >
<folder label="Grp1-2-1" >
<folder label="Grp1-2-1-1" icon="blue" />
</folder>
<folder label="Grp1-2-2" >
<folder label="Grp1-2-1-1" icon="green" />
<folder label="Grp1-2-1-2" icon="green" />
</folder>
</folder>
<folder label="Grp1-3" >
<folder label="Grp1-3-1" icon="blue" />
</folder>
<folder label="Grp1-4" icon="red"/>
</folder>
</mx:XMLList>
</mx:XMLListCollection>
</mx:Tree>
ここでのポイントは3つだ。
iconFunction="{showIcon}"
という部分と、
styleName="treeIcons"
という部分だ。
あとはもうひとつは、XML内で定義しているiconという属性だ。
では、これら3つをつなぐiconFunctionを実装していこう。
その前に、treeIconsでは、下記のようにスタイルを定義している。
.treeIcons{
blue:Embed(source="assets/blue.png");
green:Embed(source="assets/green.png");
red:Embed(source="assets/red.png");
}
もちろん、画像もそれぞれ用意してほしい。
さて、showIconメソッドであるが、
private function showIcon(item:Object):Class{
var x:XML = item as XML;
if(x){
var iconStyle:String = x.@icon;
if(x.children().length() > 0){
// folderの場合にはデフォルトのアイコンを使用する
return tree.getStyle(tree.isItemOpen(item)? "folderOpenIcon":"folderClosedIcon");
}
else if(iconStyle){
var clz:Class = tree.getStyle(iconStyle);
if(clz){
return clz;
}
}
}
return icon;
}
となる。
x.@iconでXMLのicon属性から値を取得し、その名前のスタイルを取得して、
画像クラスを返している。
また、フォルダはもともとの画像を表示するようにした。
itemRendererなどですべてを書き換えてしまう方法もあるが、アイコンだけならばこのようにできる。
また、CSSとしたことでプログラムとは別のswfに含めることができるのでメンテナンス性や、デザインの自由度も上げることができると思う。
出来上がったものはこんな感じになる。
前回、Part1では3つのクラスを継承してクラスを再定義すればいいというとこまで、中身を見ていきました。
もっと早くPart2を書こうとは思ったのですが、自分の方のクラスが閉じる場合やら、閉じない場合やら、
その他、いろいろと機能をつけてしまい、シンプルなものとはかけ離れてしまいました。
それで、とりあえず最初に戻って、白紙状態のものを再度作成することにしました。
ということで・・・・
改めて、その3つのクラスは
- mx.containers.TabNavigator
- mx.controls.TabBar
- mx.controls.tabBarClasses.Tab (タブの本体)
です。これらを再定義したクラス名を以下の通りとします。
- CustomTabNavigator
- CustomTabBar
- CustomTab (タブ本体です)
さて、今回のPart2ではとりあえず、拡張するための土台部分を作成します。
要は、これら拡張クラスを作成しますが、全くTabNavigatorと同じ動きをさせます。
さて、話はもどって、まずは3つのクラスを作成します。
最初は、もっともコアなCustomTabクラスです。こちらは、のちのちもっとも変更されるクラスですが、
このクラスが呼ばれるための仕組みを理解するということで、このクラスには最初何もありません。
[AS]
package com.coltware.cise.ui {
import mx.controls.tabBarClasses.Tab;
public class CustomTab extends Tab{
public function CustomTab() {
super();
}
}
}
[/AS]
これで、Tabと全く同じ動きをするクラスの完成です。
次に、このクラスを呼ぶクラスである、CustomTabBarクラスを作成することにしましょう。
[AS]
package com.coltware.cise.ui {
import mx.controls.TabBar;
import mx.core.mx_internal;
import mx.core.ClassFactory;
use namespace mx_internal;
public class CustomTabBar extends TabBar{
public function CustomTabBar() {
super();
navItemFactory = new ClassFactory(CustomTab);
}
}
}
[/AS]
前のクラスよりは、ちょっと手が入っています。
前回の記事でもこの部分については述べたと思います。
さて、CustomTabBarからCustomTabが呼ばれるところまでできたので、後は、CustomTabNavigatorから、
CustomeTabBarを呼ぶようにします。
[AS]
package com.coltware.cise.ui {
import mx.containers.TabNavigator;
import mx.styles.StyleProxy;
public class CustomTabNavigator extends TabNavigator{
public function CustomTabNavigator() {
super();
}
override protected function createChildren():void{
// ここで親のcreateChildrenを呼んではいけない
//super.createChildren();
if(!tabBar){
var _tabBar:CustomTabBar = new CustomTabBar();
tabBar = _tabBar;
tabBar.name = “tabBar”;
tabBar.focusEnabled = false;
tabBar.styleName = new StyleProxy(this, tabBarStyleFilters);
rawChildren.addChild(tabBar);
}
// ここで親のcreateChildrenを呼ぶ
super.createChildren();
}
}
}
[/AS]
ここは、createChildrenを再定義します。
ここでは、基本的にはTabNavigatorのcreateChildrenの中身を見て、該当の部分(new TabBar()している部分)をCustomTabBarにしてしまえばいいのですが、
super.createChildren()を最初に呼ぶのではなく、最後に呼んでください。
これで、TabNavigatorの if(!tabBar) の部分内の処理を実行しなくなります。
これで、準備完了です。
次回は、Tabの中身を実装し、閉じるボタンを作成していきます。(予定)



