Archive for 6月, 2009
前回の続きで、可変関数を利用して複数の配列をマージするユーティリティクラスを作ろうとおもったら、すでにありました。
下に、マニュアルに載っていたサンプルをもとに、引数を複数にして試してみました。
詳しくはマニュアルを見てください。
[AS]
var numbers:Array = new Array(1, 2, 3);
var letters:Array = new Array(“a”, “b”, “c”);
var numbersAndLetters:Array = numbers.concat(letters,”A”,”B”,”C”);
var obj:Object = new Object();
var lettersAndNumbers:Array = letters.concat(numbers,obj);
trace(numbers); // 1,2,3
trace(letters); // a,b,c
trace(numbersAndLetters); // 1,2,3,a,b,c,A,B,C,D
trace(lettersAndNumbers); // a,b,c,1,2,3,[object Object]]
trace(letters); // a,b,c
[/AS]
ActionScriptで、sprintf() を作りたいなーと思っていて、改めて関数のマニュアルを調べてみた。
sprintfを知らない方はこちらをどうぞ。(PHPのマニュアルにリンクしています。)
すると、やっぱりあるじゃないですか?
…(rest)パラメータ
というらしいです。
昔、Javaもなかったのですが、今はあるはずです。(うるおぼえ)
まあ、自分で使うことはあっても、つくることはないのが通常なのですが、やはり、ActionScriptでも文字のフォーマット付き、置き換えができればらくになるなーと。
そんな関数を作りたいと思っています。
でも、「…(rest)パラメータ」ってなんと読めばいいのでしょか?
読めない名称は広まらないし、説明できないのできちんと名称を付けてほしいものです。
今回は、ベクターでお絵かきです。
Flexを使っているとあまりなじみがないというか、必要がないと思われる Graphicsクラスを用いたベクターのお絵かきです。
使いどころといえば、たとえば、自作のコンテナを作るときの枠組みとか、私はパネルをリサイズするときにリサイズする対象を書くなどで必要になりました。
今回は、2つの角丸四角を書いてみました。
1つは、lineToとcurveToを用いて、つまり、直線と曲線を使って。
2つは、drawRoundRect、ずばり、角丸四角を書くための関数です。
なぜかと言えば、drawRoundRectをつかった四角が微妙にきにいらなかったので、lineToとcurveToで書いたものと違いがあるのかみてみました。
したが、その例です。
スライダーを変更すれば、角がより丸くなり、線の色と、塗りつぶす色の設定になっています。
また、上が、直線と曲線のみ、したが、drawRoundRectを使って書いたものです。
下の示すソースで書いています。
[AS]
private var sx:Number = 10;
private var sy:Number = 10;
private var cp:Number = 10;
private var boxWidth:Number = 200;
private var boxHeight:Number =100;
private function load():void{
this.drawBox();
}
private function drawBox():void{
cp = hs.value; // スライダーの値
var g:Graphics = canvas.graphics;
g.clear();
g.lineStyle(2,uint(bcolor.value),0.5); // bcolor ColorPicker(線の色)
g.beginFill(uint(fcolor.value),0.5); // fcolor ColorPicker(塗りつぶす色)
// 直線と曲線だけで角丸四角を書く
g.moveTo(sx,sy+cp);
g.curveTo(sx,sy,sx+cp,sy);
g.lineTo(sx+boxWidth-cp,sy);
g.curveTo(sx+boxWidth,sy,sx+boxWidth,sy+cp);
g.lineTo(sx+boxWidth,sy+boxHeight-cp);
g.curveTo(sx+boxWidth,sy+boxHeight,sx+boxWidth-cp,sy+boxHeight);
g.lineTo(sx+cp,sy+boxHeight);
g.curveTo(sx,sy+boxHeight,sx,sy+boxHeight-cp);
g.lineTo(sx,sy+cp);
// 角丸四角を書く
g.drawRoundRect(sx,sy+boxHeight+10,boxWidth,boxHeight,cp*2);
g.endFill();
canvas.invalidateDisplayList();
}
[/AS]
RichTextEditorを使っていて、サイズをリサイズするとツールバーのところの動きがちょっと、ほかのコンポーネントとは違う動きをする。
というのも、小さくするとスクロールバーを表示せずに、下に移動させて表示する。
まあ、俗に言うFlow レイアウトというやつだ。
FlexにはこのFlowレイアウトがないのかーとおもって、自作も考えたが結局 flexlibのFlow Containerを使ってしまいました。
(flexlibにもFlow BoxとFlow Containerって2つあるけど、どう使い分ければいいのかな?とちょっと疑問)
だけど、RichTextEditorのツールバーはどう見てもFlowレイアウトになっている。
さて、ソースを見てみたら、<mx:ToolBar>と書いてあるが、Flex Builderの「定義へ移動」とやっても移動しない。
んー、どういうことなのだろうか?
こういうときには、Linux版のflexです。
find ./ | grep ToolBar ./frameworks/projects/framework/src/mx/controls/richTextEditorClasses/ToolBar.as
ありました。
RichTextEditor専用のクラスになっているようですね。
でも、別にほかでも使えるようです。
<mx:ToolBar>
ちょっとした部品を置くのにはちょうどいいのではないでしょうか?
また、ソースも非常に短いのでContainerを自作するうえでも参考になると思います。
コンテキストメニューを調べていたら、何かちょっと思ったことができないのかなーと、思い始め、自作するのとどっちが楽なのだろうか?と思い始めた。
さて、メニューを表示するにはPopupManagerを使えばいいのだから簡単だ・・・・・
ちょっとサンプルを作ってみようと思ったが・・・・
はて、表示されたメニューをクリックされた時はいいが、表示されたメニューをクリックされずにキャンセルしたい場合にはどうすればいいのだろうか?
閉じるメニューがあれば閉じられるが、そんなUIはコンテキストメニューとは言い難いだろう。
そんなときに、最近感覚がわかってきたのが、それと同じような動きをするようなflexのコンポーネントのソースをみればいいのだ。
今回はComboBoxとDateFieldを見てみた。
両方とも、POPUPされるが、ほかの操作に移ると消える。
だったら、この部分のソースを見てしまえばいい。
ということでわかったのが、FlexMouseEventの
を使えばいいということだ。
なにやら意味のわからないタイトルになっていますが、
なんとなく書いている flexのmxmlコンポーネント。
<mx:Canvas> <mx:Label /> </mx:Canvas>
なんて書いていたと思ったら、
<mx:DataGrid>
<mx:columns>
<mx:DataGridColumn />
</mx:columns>
</mx:DataGrid>
のように書いていた。
columnsなんてコンポーネントはないはず。
ここでcolumnsなんて書かなくても、mx:DataGridColumnでいいのになーと思っていた。
また、タグの下に書いたものはその親のaddChildによって追加されると思っていた。
しかし、それではいろいろとつじつまがあわないのも分かっていた。
なんとなーく、flex frameworkのソースをぶらぶらと気になる部分を見ていたら気がつきました。
単なるDataGridのsetterでcolumnsを設定していたという事でした。
[AS]
[Bindable("columnsChanged")]
[Inspectable(arrayType="mx.controls.dataGridClasses.DataGridColumn")]
override public function set columns(value:Array):void{
}
[/AS]
という事なんですね。
だから、columnsの子はDataGridColumnになり、Array型として登録していたということでした。
だから、addChildじゃないんだーーー。
ルールといえばルールだが、エンジニアとしてはソースでわかると安心できますね。
頭での理解がたりなくても、肌の理解度がふかまりますよね。
いやー、この辺もわかった上でドキュメントとか見れば書いてあるんでしょうね。
わからないと、書いてあってもそれが読めないんですよね。
改めて、mxmlの基本を勉強しました。
これで、もう少しは独自コンポーネントを作りやすくなりました。
今まで、CheckBoxや、アイコンを表示する方法を述べてきたが、デフォルトのアイコンを表示しない方法を紹介します。
アイコンを表示しない方法もそうだが、nullを参照させるってこともできるんだ。。。。とちょっと感心してしまった。
<mx:Tree id="categoryTree" width="100%" minWidth="200" height="100%"
defaultLeafIcon="{null}" folderClosedIcon="{null}" folderOpenIcon="{null}" >
以上のように、
- defaultLeafIcon
- folderClosedIcon
- folderOpenIcon
にnullを設定すればいい。
Tree関連で、フォルダをソースの中で folderと言ってみたり、 branchと言ってみたり、自分もプログラムを作っていると、表記ゆれが発生してしまう。結構、作った時期が違うことをがほとんどだが、たまーに、わざと表記を変えることもある。
(自分も、その理由を忘れてしまう事があるが・・・)
余談だが・・・
DBのデータを利用してtreeを作成するにはどうやったらいいのかなーと頭にとどめながらふと、Flex SDKの中でTreeでfindしてみたら、
FilesystemTreeというものがあり、そこで自分でTreeに提供するデータを自作するためのいいサンプルがあった。
私は今まで、DataProviderを自作すればいいのかなと思って、探していたが、DataDescriptorというのを自作すればいいらしい。
ようするに、DataProviderはデータそのものであり、DataDescriptorはデータの表記方法という感じで分かれているという感じであろうか?
(ちょっとちがうかな???)
というわけで、DataDescriptorを自作してDBのデータからTreeを作成する。
ちなみにDB(テーブル)はこのようになっている。
var sql:String = "CREATE TABLE IF NOT EXISTS tree(" +
"uid INTEGER PRIMARY KEY AUTOINCREMENT," +
"pid INTEGER NOT NULL," +
"branch BOOL NOT NULL," +
"title VARCHAR(100) NOT NULL" +
")";
これはあらかじめ作成しておいてください。
また、データもあらかじめ作成しておいてください。
insert into tree(pid,branch,title) values(0,1,'title1'); insert into tree(pid,branch,title) values(0,1,'title2'); insert into tree(pid,branch,title) values(0,1,'title3'); insert into tree(pid,branch,title) values(1,0,'title1-1'); insert into tree(pid,branch,title) values(1,0,'title1-2'); insert into tree(pid,branch,title) values(1,0,'title1-3');
のように作成してもらえればと思います。
branchフィールドは、単純にbranchか?どうか?ということを示すフィールドで、
pidは、親IDなので、親となるuidの値をいれてもらえればと思います。
さて、これで表示するためのデータが作成できました。
これを表示するための、DataDescriptorを自作していきます。
FilesystemTreeによれば、DataDescriptorはFileSystemTreeDataDescriptorというクラスで、
DefaultDataDescriptorを継承しています。
これとまったく同じようにして作成します。
このクラスでoverride しているのは3つのメソッドのみです。
- isBranch
- hasChildren
- getChildren
の3つです。
これらを実装したソースを以下に示します。
package com.coltware.cise.db
{
import flash.data.SQLConnection;
import flash.data.SQLResult;
import flash.data.SQLStatement;
import mx.collections.ArrayCollection;
import mx.collections.ICollectionView;
import mx.controls.treeClasses.DefaultDataDescriptor;
public class DBTreeDescriptor extends DefaultDataDescriptor
{
private var _conn:SQLConnection;
/**
* テーブル名
*/
public var tableName:String = "tree";
public var pkeyField:String = "uid";
public var parentField:String = "pid";
public var branchField:String = "branch";
public function DBTreeDescriptor(){
super();
}
public function set sqlConnection(conn:SQLConnection):void{
this._conn = conn;
}
/**
* データ表示のためのTreeのDataProviderに設定するデータを取得する
*/
public function getRootCollection():ICollectionView{
var root:Object = new Object();
root[this.pkeyField] = 0;
root[this.branchField] = true;
root[this.parentField] = -1;
return this.getChildren(root);
}
/* ************** 以下3つのoverrideメソッドは呼ばれる順番に記述 *********** */
/**
* branch(フォルダ)かどうか?フォルダならば、xxxChildrenが開かれたときに呼ばれる
*/
override public function isBranch(node:Object, model:Object=null):Boolean{
return node[this.branchField];
}
/**
* 子ノードを持っているか?持っていれば、 getChildren()メソッドが呼ばれる
*/
override public function hasChildren(node:Object, model:Object=null):Boolean{
var stmt:SQLStatement = new SQLStatement();
stmt.sqlConnection = this._conn;
stmt.text = "SELECT count(*) as cnt FROM " + this.tableName + " WHERE " + this.parentField + " = :KEY";
stmt.parameters[":KEY"] = node[this.pkeyField];
stmt.execute();
var ret:Object = this.getRow(stmt);
if(ret != null && ret.cnt > 0 ){
return true;
}
return false;
}
/**
* 子を返す。返されたノードの isBranchメソッドによるチェックで再帰的に子ノードが展開される
*/
override public function getChildren(node:Object, model:Object=null):ICollectionView{
var stmt:SQLStatement = new SQLStatement();
stmt.sqlConnection = this._conn;
stmt.text = "SELECT * FROM " + this.tableName + " WHERE " + this.parentField + " = :KEY";
stmt.parameters[":KEY"] = node[this.pkeyField];
stmt.execute();
var result:SQLResult = stmt.getResult();
if(result != null && result.data != null){
if(result.data.length > 0 ){
return new ArrayCollection(result.data);
}
}
return new ArrayCollection();
}
private function getRow(stmt:SQLStatement):Object{
var result:SQLResult = stmt.getResult();
if(result != null && result.data != null){
if(result.data.length > 0 ){
return result.data[0];
}
}
return null;
}
}
}
一応、テーブル名やフィールド名は変えらるようにしていますが、データ構造などは変更できません。
また、利用するには、
var conn:SQLConnection = new SQLConnection();
var file:File = File.applicationStorageDirectory.resolvePath("tree.sqlite");
conn.open(file);
var descriptor:DBTreeDescriptor = new DBTreeDescriptor();
descriptor.sqlConnection = conn;
dbtree.dataDescriptor = descriptor;
/* DataProviderに初めのArrayCollectionだけは取得して設定する */
dbtree.dataProvider = descriptor.getRootCollection();
dbtree.labelField = "title";
のような感じです。
これができれば結構Treeが使いやすくなるのではないでしょうか?
今まで、データをすべて作成してchildrenプロパティに入れていましたが、その苦労をしていたのが無駄になってしまいました。
オブジェクトのパラメータの一覧を取得したいが、そのためのメソッドがない!!あれ!?といつも思ってしまう。
var obj:Object = new Object();
obj.param1 = “foo1″;
のようにActionScriptではオブジェクトのパラメータが動的に作成できる。(ちょっと、Javaなどに慣れていると気持ちが悪い。文法がJavaに近いし、Eclipse(Flex Builder)でプログラミングしていると、脳がその考え方になってしまう。)
で、後で定義したプロパティ名一覧を取得したいなーと思って、Objectのリファレンスを見ると、指定したプロパティがあるか?ないか?はあるが、プロパティ名一覧を取得するメソッドがない。
なぜだ・・・とちょっと思ってしまう。が、for .. inでプロパティ名をとれるのを忘れてしまっている。
また、for .. in とならんで、 for each.. in もあるので、ちょっと整理。
[AS]
var obj:Object = new Object();
obj["@param1"] = “foo1″;
obj.param2 = “foo2″;
for(var str:String in obj){
trace(str);
}
[/AS]
“@param1″と”param2″が出力される。
[AS]
var obj:Object = new Object();
obj["@param1"] = “foo1″;
obj.param2 = “foo2″;
for each(var item:Object in obj){
trace(item);
}
[/AS]
“foo1″と”foo2″が表示される。
とあたまではわかっているのですが、なかなか体が覚えてくれません。



