Archive for the ‘UI’ Category

Flexでは、ダイアログをPopUpManagerを使って作成することができる。
また、作成時に画面の中央にダイアログを表示したい場合には

private function createDialog():void{
  var popup:IFlexDisplayObject = PopupManager.createPoopup([親画面],[Dialogのクラス],true);
  PopupManager.centerPopup(popup);
}

のようにすればよい。
ここで、[Dialogのクラス]は、通常MXMLのようなものをさすと思う。

でも、小さい画面で開いていたが、ダイアログが見えないので画面を最大にしたとしても、
このままではダイアログは中央には移動しない。

ここで、親画面のリサイズとともにダイアログも中央の移動してほしい場合には
画面の更新とともに、以下のようなメソッド(関数)を作成しておき実行する。


private function updateEnd():void{
  if(_curPopup){
    PopUpManager.centerPopUp(_curPopup);
  }
}

ここで、_curPopupは先の作成時の処理を

private var _curPopup:IFlexDisplayObject = null;
private function createDialog():void{
  _curPopup = PopupManager.createPoopup([親画面],[Dialogのクラス],true);
  PopupManager.centerPopup(_curPopup);
}

のように変えておけばよい。

また、画面の更新時の処理はMXMLであれば、

<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
	updateComplete="updateEnd()" >

のように、updateCompleteのプロパティ(イベント)を使うのが楽だ。
ちなみに、このイベントは正確にはmx.events.FlexEvent.UPDATE_COMPLETEになるので、AS内であればこのイベントで処理をする。

ViewStackを使っていて、子コンポーネントでの表示切替時でちょっと気にしなければいけない点があったので記録。

ViewStackなどは子コンポーネント同時に表示するわけではないのでちょっと気にしなければならないのが、creationPolicyプロパティ。
これは、子のインスタンスルールなのだが、通常、何も指定していないときには、子ビューがインスタンス化されていない可能性があるということです。

これは遅延インスタンスという機能があり、実際に必要になる(見る必要がある)までインスタンスを遅らせるというものです。
ここで、ちょっと注意なのが「子ビュー」のインスタンスということで、そのクラスのインスタンスではありません。

ですので、普通の部品のようにMXML内の要素に、自分自身のcreationCompleteなどの処理でアクセスしようとしてもエラーになってしまうというわけです。
したがって、creationCompleteで処理をするには、「子ビュー」の要素にcreationCompleteのプロパティを設定すれば、
想定したようになるというわけです。

たとえば、

<mx:ViewStack>
<foo:View>
</mx:ViewStack>

<foo:View creationComplete="init()">
<mx:Canvas id="cs" />
</foo>

しかし、init()処理の中ではcsにはアクセスできません。これは、遅延インスタンス機能によりまだ、インスタンス化されていないからです。
したがって、

<foo:View >
<mx:Canvas id="cs" creationComplete="init()" />
</foo:View>

にすればいいというわけです。

まあ、creationPolicyをもっと良く知れば、これ以外にもそのアプリケーションならではのもっといい方法が見つかるとは思います。

TextInput(その用途はないと思うが、 もしくはTextArea)でパスワードフィールドとして表示させる場合には、

<mx:TextInput displayAsPassword="true" />

とすればよい。

これがいつも忘れてしまって、毎回調べる羽目になる。
HTMLのinput type=”password” になっているのだからせめて “password”から始まる名前にしてほしい。
そうすれば、覚えられなくてもコード補完で何とかなる。

もしかして、”displayAs_____”って他でもそういう使い方にもしかしてFlexではなっているのかな?と思って調べたが、これ以外にはなかった。

あまりにも久しぶり(2か月)で自分でもどこまで記述したか忘れてしまったので、再度、過去を整理。
ちょっと、ZIPライブラリにどっぷりつかりすぎてしまいました。

過去、どのような記事を書いたかといえば、

flex:DataGrid sqlを使ってdataProviderを自作する ( Part 1 )
flex:DataGrid sqlを使ってdataProviderを自作する ( Part2: 大きなデータを扱う時にページングって本当に必要ですか?)

ただ、内容的には、Part1の続きです。(Part2はどちらかといえば、番外編コラム的内容です。)

気を取り直して、DataGridのdataProviderを作成するために必要なのは、

TableCollectionView
ICollectionViewを継承したクラス。Array(ArrayListのほうが正しい)に対するArrayCollectionの関係のクラスです
TableList
こちらは、データそのものを表すクラス。今度はArrayCollecitonに対するArray(ArrayList)に相当するクラスです。

の2つのクラスです。
そうそう、思い出してきました。

ArrayとArrayCollectionの関係も参考になるので、この関係がわからない方は、こちらも読んでください。

TableListのクラスはIListインターフェースを実装する必要があります。
このインターフェースのすべてを実装する必要はありません。

そもそも大量なデータの参照の場合には、通常、登録処理はドラッグ&ドロップなどでやるということはあまりないはずです。
したがって、ここのインターフェースを使って登録や・更新されるようなことは今回考えません。

では、実装するメソッドは何があるの・・・・って

  • get length():int – プロパティとなっていますが、getterなので・・
  • getItemAt():Object
  • getItemIndex():Object

たったこれだけです。
もちろん、IListのインターフェースを満たす必要がありますので、それ以外のメソッドはnullを返すようなメソッドを実装しておいてください。
(本当は、ランタイムエラーを返すようにしておいたほうがいいかもしれません。はっきり使えないことがわかりますので・・・)

さて、length()では

SELECT count(*) as count FROM [テーブル名];

のようなSQLを実行すればいいわけですので、TableListクラスでは逆にSQLが実行できるような環境設定が必要というわけなので、

package com.coltware.commons.db
{
  public class TableList extends EventDispatcher implements IList{

    private var _conn:SQLConnection = null;
    private var _tableName:String;

    public function set sqlConnection(conn:SQLConnection):void{
      this._conn = conn;
    }
    public function set tableName(name:String):void{
      this._tableName = name;
    }
  }
}

のように、SQLの接続とテーブル名だけでも外部から設定しておけるようにしておけば当面は間に合うと思います。
上のコードにIListのインターフェースを満たす実装をしていきます。

以下が、lengthのgetterメソッドです。

public function get length():int
  var stmt:SQLStatement = new SQLStatement();
  stmt.sqlConnection = this._conn;
  stmt.text = "SELECT count(*) as count FROM " + this._tableName;
  stmt.execute();
  var result:SQLResult = stmt.getResult();
  if( result != null && result.data != null ){
    var retObj:Object = new Object();
    var size:int = result.data.length;
    if(size > 0 ){
      return result.data[0]["count"];
    }
  }
  return 0;
}

ここで、0以上が帰れば、次に呼ばれるのは getItemAtです。

public function getItemAt(index:int, prefetch:int=0):Object
{
	var sql:String = "SELECT * FROM " + this._tableName + " LIMIT 1 OFFSET " + index;
	//  後は、length()のメソッドを参考に実装をしてください。
}

のようになります。
ここで、SQLとして、「あれ?」と思う部分は・・・・
そう、ソート部分がないのです。とりあえず、ちょっと話がややこしくなるので、今のところはここは無視しください。
(後で、ソートについても実装していきます。)

また、1行、1行、SELECTするのってどうなの?と思う方もいるかもしれませんが、JOINなどをしないのであれば毎回このように取ってきても問題ないと思います。
だって、表示されている部分しか取ってきませんし・・・
(気になる方は、キャッシュ機能(ページング機能)を実装してください。)

後は、getItemIndexですが、ただし、ちょっと疑問?な点があります。
オブジェクトからそのオブジェクトが何番目のオブジェクトなのか?ってどうやってわかるの?
だいたい、何番目ってどこを基準に?
って話です。

しかし、そもそも、getItemAtで、「n番目のデータをください!」ってお願いされているのですから、
ここで、このオブジェクトはn番目ってしるしをつけておくとよいでしょう。
そして、基準もここである前提(ソートが関係)のもと聞いているのですから、問題ないと思います。

私はそのために管理オブジェクトを作るのも何なので、

public function getItemAt(index:int, prefetch:int=0):Object
{

  : ( 省略 )
  var result:SQLResult = stmt.getResult();

  if( result != null && result.data != null ){
     var obj:Object = result.data[0];
     obj['_internal_index'] = index;
     return obj;
  }
  return null;
}

のように、オブジェクト自身に新たにプロパティを設けて、ここに保存してしまいます。
まず、_internal_indexなんているカラムは作らないと思います。

これで、

public function getItemIndex(item:Object):int
{
  return item['_internal_index'];
}

のようになるわけです。

さて、これで使わないが宣言だけ必要なメソッドを実装すれば完成です。

次回は、TableCollectionViewを実装、そして次に、ソート部分の対応をしていきます。

さて、DataGridのdataProviderを自作する件のpart2であるが、
そもそもDataGridとdataProviderはどのようなやりとりをしてデータを表示しているのだろうか?ということを書きたいと思います。

というのも、DataGridに関する情報をみていると雑誌(たぶん)などでも大きなデータでスクロールバーを動きをみて、ページング対応が必要とか記述してある。
ほんとにこんなに難しいのだろうか?と疑問に思ったのがきっかけだ。

(今回は sqlは関係ありませんが、中身がわかればどのようなクエリを発行すればいいか分かるので、参考になると思います。)

確かに、データ取得(バックエンドの都合)によるページングは必要かもしれないが、UIの動きはまったく関係なく解決できるはずだ。
Flex側の都合ではいらないのではないだろうか?ということを思った。

まあ、私もJavaのSwingや、EclipseのRCPを作った時にこのへんは勉強したなー。

(利用者がそれを知った上で、ページングをUIに望んでいるなら別ですが・・・
たまにいるんですよね。俺がほしいデータは500ページあたりにあるんだよってことが分かるくらいにつかいこんじゃう職人が・・・)

話はもどって、DataGridとdataProviderではどのようなやりとりが行われているのか、わかりやすいように会話風にしてみました。

登場人物は
UI君       :DataGridそのもの
プロバイダさん  :ICollecitonViewを実装したクラス(例:ArrayCollection)
下請け      :IListを実装したクラス ( 例: ArrayList ※クラスはpublicですが、ドキュメントは@private なので見えません)
神(ユーザ)   :利用する人。UI君を雲の上から操作し、すごくわがままなこともできる。

では・・・

[UI君] : さっそく、データを表示したいんだけど、全部で何件あるの?
[プロバイダさん]: 下請けさん、何件なの?
[下請け] : プロバイダ様、1000万件です。( get length() )
[プロバイダさん]: 全部で1000万件だよ。 ( get length() )

[UI君] : 1件目ちょうだい!
[プロバイダさん]: はいよ、下請けさんお願いね。
[下請け] : できました。お願いします。( getItemAt() )
[プロバイダさん]: できましたよ。UI君。
[UI君] : じゃ、次2件目!
[プロバイダさん]: はいよ、例のごとくよろしく。
[下請け] : できました。お願いします。
(これが続く。。。)
[UI君] : じゃ、15件目!
[プロバイダさん]: はいよ、またお願い。
[下請け] : できました。お願いします。
[プロバイダさん]: できましたよ。UI君。
[UI君] : おっと、表示するのは15件だからこれで終わりだな。

[神(ユーザ)] : スクロールバーを下げるから、もっと下を見せなさい。
[UI君] : おー、次の仕事を頼むぞ。プロバイダさん。次16件目
[プロバイダさん]: はいよ
[下請け] : できました。お願いします。
[UI君] : 次17件目
[プロバイダさん]: はいよ
[下請け] : できました。お願いします。

[神(ユーザ)] : こりゃ、だめだ。みつからん。逆から見てみよう。ソート!
[UI君] : あちゃー、今までデータを順番にもらっていたけど。無駄だったよ。今までのお願いはなしね。
[プロバイダさん]: りふれーしゅ!!
 ※ ここは自作が必要ですが・・・ 、どうやらこの処理で IList のtoArray()が動くようです。
 このメソッドがインターフェースに入っていなければ、IListもっと良くなると思うのですが。
 これじゃ、データをすべてArrayで返さなければいけないみたいですよね。上の例だと1000万件も。
 でも、実際はこの処理をすべて自作してしまえば、このメソッドは使われない(今のところ)ので、実装ではエラーでも返しちゃいましょ。
 (これでほかの場所でも実は使っているのですが、そこがわかりますよ。)
  
[UI君] : そうそう、ソートの条件はこれね。これからもよろしく。
[プロバイダさん]: 了解!( set sort )
 ※ プロバイダを自作した場合にいは、ソートの概念の実装も自作が必要です。
フィールド名と順番(ASC,DES)が来るから、DBを使っていればその条件を渡してあげればいいだけですが・・・
 このあたりは、下請けさんにもソートの条件を伝える必要がありそうです。
 というのも、下請けさんは公式にはソート条件をもらうすべを持っていません。こっそり教えてあげましょう。
  
[UI君] : じゃ、1件目ちょうだい。
[プロバイダさん]: はいよ。
[下請け] : できました。お願いします。

だけど、プロバイダさんとUI君は実はもっと愛情を注げば(拡張すれば)、もっと賢くなれるようなのです。
こんな感じです。
[UI君] : 次の18件目ちょうだい。
[プロバイダさん]: 了解。じゃ、下請けよろしく。
[下請け] : ただいま、データが用意されていませんので、少々お待ちいただけないでしょうか?
[プロバイダさん]: あちゃー、それは今用意できていないよ。そんな急に言われても困るよ。UI君。( throw ItemPendingError )
[UI君] : んー、それじゃデータが用意できたら教えてよ。
時間がながれる・・・・
[下請け] : データができましたので、お知らせ致しました。
[プロバイダさん]: UI君、待たせたね。できたよ。
[UI君] : おー、ありがとう。じゃ、表示させてもらうよ。

でも、このあたりはインターフェースだけあるようで、実装はありません。
私も今のところ必要性を感じないので見ていませんのでご了承ください。

さてさて、このようにUIとプロバイダではデータが大きくなっても対応できるようなつくりになっています。
あとは、プロバイダがどのようにデータを取得するかは 下請け(IList)の実装にかかっているのです。

下請け(IList)が必要な時に1件、1件取ってきてもsqliteを使っていれば問題ないようにも思いますし、
ネットワーク越しに前もってまとめてデータを取得しておいて1件、1件渡せば、処理はページング処理(キャッシュ処理)が必要になります。
ただ、ページング処理をしてしまうと、ページアウト処理(というか、作ったデータをいらないので消す処理)が必要になってしまいます。
(このあたりは、調べきっていませんが、IViewCursorあたりがそんな役目をするためのもののような気がします。)

でも、DataGridとしてUI側の対応は何も変わらなくても問題ないということがわかっていただけるのではないでしょうか?

追記
それでも、DataGridで大きなデータを扱うときにバックエンド側や、データ自体の問題ではなくUI側の問題で対応が必要な情報をおもちでしたら、
教えていただけないでしょうか?
いずれ、私もその問題にぶつかると思いますので助かります。

RSS
Add to Google

カスタム検索
ソフトウェア&ライブラリ


ライブラリ
airxmail(en)
AIR版メール送受信ライブラリ
airxzip
AIR版ZIP圧縮・解凍ライブラリ
カレンダー
2010年8月
« 7月    
 1
2345678
9101112131415
16171819202122
23242526272829
3031  
アーカイブ
にほんブログ村 IT技術ブログへ