JBuilderおよびJavaPosseで知られるJoe Nuxollが言うように、Javaの設計上の落とし穴のひとつはJavaのコンポーネントモデルです。これはコンポーネントの完全言語サポートを提供するFlexなどのシステムと比較すると特に明確になります。
Flexには、プロパティとイベントのサポートが組み込まれているだけでなく、コンポーネントのレイアウトと構成に使用できる高度な抽象言語(MXML)が含まれているため、開発プロセスを大幅に単純化できます。MXMLから直接新しいコンポーネントを作成することもできますが、通常はActionScriptを使用して作成する方が一般的です。この記事では、Flexでのコンポーネントを作成する方法、およびこれらのコンポーネントを使用した生成アプリケーションがどれほど優れているかについて説明します。
ご存知のとおり、ActionScriptは多くの点でJavaに似ていますが、ActionScriptには、Javaにない、時間を節約できる機能もあります。
すべてのFlexコードを手で作成し、mxmlc(無料のコマンドラインコンパイラ)を使用してコンパイルすることもできますが、Eclipse上に構築され、コマンド完成、コンテキストヘルプ、ビルトインのデバッグなどを備えたFlex Builderを使用した方がずっと簡単です。この記事の例の一部では、(Webサンドボックスに限定されるのでなく)ローカルマシンのファイルやその他の要素にアクセスするデスクトップアプリケーションを作成できるベータ機能、AIRを使用しています。AIRサポートを含むFlex Builderのベータ版は、Adobe Labs*からダウンロードできます。(これらの例は、コマンドラインコンパイラを使用して構築することも可能です)。
このチュートリアルをフォローするには、次のソフトウェアがインストールされている必要があります。
この記事をフルに活用するには、MXMLおよびActionScript*について実用的な知識を持っている必要があります。
コンポーネントを作成する最も簡単な方法は、MXMLを使用することです。最初に、新しいアプリケーションを作成します。Flex Builderメニューから、ファイル/新規/Flexプロジェクトを選択し、ウィザードに情報を入力します。次に、Flex Builderメニューで、ファイル/新規/MXMLコンポーネントを選択します。継承する基本コンポーネントを選択するオプションなどが含まれた簡単なウィザードが作成されます。ここで「Button」を選択し、新しいコンポーネントに「RedButton」と名前を付けました。以下の内容を含むRedButton.mxmlというファイルができました(ファイル名がコンポーネント名になります)。
<?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="/2006/mxml"></mx:Button>
新しいコンポーネントはButtonに基づいており、次のようなアプリケーションで使用できます。
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="/2006/mxml" layout="absolute" xmlns:local="*"> <local:RedButton label="Not so Red"/> </mx:Application>
ボタンの元の機能(ここで使用するlabelを設定する機能も含む)はすべて保存されます。
ボタンの背景色をコンポーネントの名前どおりに赤に設定したり、ツールヒントやデフォルトのclickアクションを追加したりすることができます。
<?xml version="1.0" encoding="utf-8"?>
<mx:Button xmlns:mx="/2006/mxml" fillColors="['red', 'blue']" toolTip="A Red Button" click="clicked()">
<mx:Script>
<![CDATA[
private function clicked():void {
label = "Quite Red";
setStyle("fillColors", ['red', 'red']);
}
]]>
</mx:Script>
</mx:Button>
XMLで作業をしているため、clicked()関数はCDATAブロックのScriptタグ内に定義されます(Flex Builderによって自動挿入されます)。この関数定義には戻り値のvoidも含まれていることに注意してください。オプションのスタティック型指定は、Flex Builderでの優れたコンテキストヘルプ作成とコマンド完成を可能にするActionScript機能です。
この方法を続けてコンポーネントをさらに洗練したものにすることもできますが、MXMLコンポーネントは単純な機能を作成する場合に最適です。複雑なMXMLコンポーネントの作成はともすれば退屈になりがちなので、ActionScriptで直接作成した方が良いでしょう(いずれにしてもMXMLはコンパイラでActionScriptに変換されます)。
また、Flex BuilderにはActionScriptコンポーネントの作成を支援するウィザードがあり、ファイル/新規/ActionScriptクラスを選択してアクセスできます。すべてのクラスはJava同様パッケージに入っていなければならないため、パッケージ名を選択するオプションがあります(選択しない場合、名前の付いていないデフォルトのパッケージができます)。ウィザードではスーパークラスも選択できます。次の例では、コンストラクタのフレームワークを自動生成するようにウィザードに指示しました。
package SimpleComponents {
import mx.controls.Button;
public class ASRedButton extends Button {
public function ASRedButton() {
super();
}
}
}
Flex Builderでimportステートメントが自動作成され、ファイル名はASRedButton.asになります。コンポーネントはActionScriptで作成されますが、MXMLとして使用することができます。
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="/2006/mxml" layout="absolute" xmlns:local="*" xmlns:SimpleComponents="SimpleComponents.*"> <SimpleComponents:ASRedButton label="Not so Red"/> </mx:Application>
Flex BuilderでASRedButtonと入力しようとすると、名前が自動入力され、タグの前にXML名前空間が追加されて、Applicationタグにxmlns属性が追加されました。最終的には無料のコマンドラインコンパイラを使用する計画でも、Flex Builderの無償ダウンロードを最初に使って、このような詳細に関するヘルプを役立てることをお勧めします。
ActionScriptでは、もっと洗練されたコンポーネントを簡単に作成できます。ASRedButtonにRedButtonと同じ動作を設定できます。
package SimpleComponents {
import mx.controls.Button;
import flash.events.MouseEvent;
public class ASRedButton extends Button {
public function ASRedButton() {
super();
label = "Reddish";
setStyle("fillColors", ['red', 'blue']);
toolTip="A Red Button";
}
override protected function clickHandler(event:MouseEvent):void {
label = "Quite Red";
setStyle("fillColors", ['red', 'red']);
}
}
}
イベントに応答する方法は多数ありますが、ActionScriptコンポーネントではメソッドをオーバーライドするのが最も簡単な方法です。overrideキーワードを使用すると、新しいメソッドを誤って作成する(目的の結果が得られない)のを確実に防ぐことができます。
importステートメントは自動的にFlex Builderに含まれていました。
MXMLではpublicフィールドを作成して読み取り可能、書き込み可能な属性を作成することができますが、ActionScriptにはプロパティの読み取りと書き込みのメソッドを指定するgetキーワードとsetキーワードもあります。Labelサブクラスで両方のアプローチを見ることができます。
package SimpleComponents {
import mx.controls.Label;
import flash.events.Event;
public class MyLabel extends Label {
public var labelStates:Array = ["Ontological", "Epistemological", "Ideological"];
private var state:uint = 0; // unsigned integer
public function set textValue(newValue:String):void {
text = newValue;
}
public function get textValue():String {
return text;
}
public function onClick(event:Event = null):void {
text = labelStates[state++ % labelStates.length];
}
}
}
ここではlabelStatesでArrayであるオプションのスタティック型指定を使用しています。Arrayを作成して値を入力する方法は多数ありますが、角括弧を使用するのが一番簡単です。Arrayは型指定されず、Objectを保存するだけです。ただし、ダイナミック型指定メカニズムで結果を処理できる場合は、Arrayから項目を引き出すときのダウンキャストが強制されることはありません。
labelStatesはパブリックなので、MXML属性を設定した場合にアクセスできます。textValueにはgetメソッドとsetメソッドの両方があり、プロパティとして変更が加えられた場合にコードを実行できます。ここではデモンストレーションのため、textフィールドを割り当てました。
また、labelStates配列の要素からtextフィールドを循環するonClick()メソッドも追加しました。onClick()ではevent引数も受け取りますが、デフォルトはnullなので、オプションです。これをイベントリスナーに使用することもできますが、これについてはまもなく紹介します。このコンポーネントの使用例は次のとおりです。
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="/2006/mxml" xmlns:SimpleComponents="SimpleComponents.*" >
<SimpleComponents:MyLabel fontSize="24" id="display" textValue="Hello, World!" click="display.onClick()" />
<mx:Button click="if(display.labelStates.indexOf('Logical') == -1) display.labelStates.push('Logical')" />
</mx:Application>
MyLabelのfontSizeなどのプロパティには引き続きアクセスできます。
textValueに割り当てることで、ラベルテキストに「Hello, World!」と表示されます。clickがonClick()に割り当てられるようにするには、MyLabelインスタンスにオブジェクト識別子が必要になるので、displayのidを指定します。
Buttonのclickに割り当てられているコードは、関数を参照できるだけに留まるのでなく、インラインでコードを定義することもできることを示します(ただしすぐに乱雑になってしまう可能性があります)。「Logical」がリストに追加されていない場合は追加します。
Flashではすべてがイベントベースなので、プログラマーとしてフレームワーク生成イベントかユーザ生成イベントのどちらかにかかりきりになってしまうことがあります。アプリケーションのライフサイクルの特定の場所に一部のコードを挿入する場合、creationCompleteのような適切なフレームワークイベントを見つけます。これはアプリケーションを構築した後に行います(他にも多くのフレームワークイベントをオンラインヘルプシステムから検索できます)。
簡単な例として、タイマーを使用してMyLabelを実行できます。
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="/2006/mxml" xmlns:SimpleComponents="SimpleComponents.*" creationComplete="init()">
<SimpleComponents:MyLabel fontSize="24" id="display" textValue="Hello, World!" click="timer.stop()" />
<mx:Script>
<![CDATA[
private var timer:Timer = new Timer(1000);
private function init():void {
timer.addEventListener(TimerEvent.TIMER, display.onClick);
timer.start();
}
]]>
</mx:Script>
</mx:Application>
ここでは3つの異なるイベントを紹介します。
一般に、イベント処理はこんなに簡単です。Javaイベント処理と比較してコードが簡潔であることに注意してください。
Flexには1つのコンポーネントで別のコンポーネントのデータ変化に応答できるデータバインディングもあります。これは通常多くのイベント処理コードを必要とする共通の活動として見受けられるため、Flexデザイナーがプログラマーの作業を引き受けました。データバインディングを使用するカウンタは次のとおりです。
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="/2006/mxml" creationComplete="init()">
<mx:Label fontSize="24" text="{count}"/>
<mx:Script>
<![CDATA[
private var timer:Timer = new Timer(1000); [Bindable]
public var count:uint = 0;
private function init():void {
timer.addEventListener(TimerEvent.TIMER, function(event:Event):void {count++});
timer.start();
}
]]>
</mx:Script>
</mx:Application>
text="{count}"で、波括弧はtextフィールドをcount varにバインドします。これは[Bindable]注釈で変更されるので、コンパイラで必要なイベント生成と処理がすべて結合されます。タイマーが発生するたびにcountが変更され、これに応答してLabelのtextフィールドも変化します。
この例では、インライン関数(別名lambda)をイベントリスナーのターゲットに使用した場合も示しています。
余談ですが、MXML内にこのようなインラインコードを作成すると、コンパイラではこのコードを保持するクラスとそのクラスのインスタンスが作成されます。小さいコードのブロックでは便利かもしれませんが、そのコードを大きくしたり複雑にしたりする場合はわかりにくくなることがあります。その場合は別のActionScriptファイルに移動することをお勧めします。
最後は、AIRを使用してディスクのローカルファイルを読み取る方法を例示する、さらに洗練されたコンポーネントについて説明します。その他、正規表現を含めたActionScript構文についても詳しく見て行きます。
このコンポーネントはFlex Jamをサポートするため、特に、Flexを初めて使用し、手順ごとの指示が必要な方々を対象に作成されました。プログラム学習は昔ながらの方法です。練習を通じて学習することに重点が置かれ、すべての回答を一度に与えるのでなく、一連のヒントや回答を通じて進められるため、各段階で発見と学習を体験できます。
Flexにはプログラム学習に最適なAccordionというコンポーネントがあります。このコンポーネントにはスライド式の「ウィンドウ」のセットが含まれているため、1段階上達するごとに少しずつ詳細が明らかになります。多数の練習問題を作成し、多くの変更も加えているので、スタティックなMXMLファイルとして設定するのは大変です。そこでAccordionから新しいコンポーネントを継承し、テキストファイルを読み取って自動構築するように教えます(ここでAIRを使用します)。
最初に、練習問題、ヒント、中間ソリューション、手順について説明する簡単な言語を作成しました。'.'で始まるすべての行にはコマンドが含まれています。この例では、Exercise1.txtをディレクトリ1. Basicsに作成します:
.ex
Install Flexbuilder. Test it by creating "Hello world" in both AIR and Flex.
.h1
Go to http://labs.adobe.com/ to get the public beta of FlexBuilder 3 (This includes support for AIR).
.h2
Select File|New|Flex Project and follow the instructions to create a new Flex app. The second page allows you to specify either Flex or AIR.
.h3
Place a Text component in the application. Type a '<' and begin typing "Text" and you'll see the context help pop up the possibilities. Choose and press Return to insert it, and close the tag with />.
.s3
<mx:Text />
.h4
Now set the text property to "Hello, World!". With your cursor inside the tag, begin typing "text" and use command completion.
.s4
<mx:Text text="Hello, World!" />
.h5
You may also want to choose the Design button that you'll see in the application area, then click and drag a Text component to add it to your application Panel.
Use Flex Properties to add text "Hello World." If you use design mode, be sure to switch back to Source mode and look at the generated MXML for your program.
.final
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication
xmlns:mx="/2006/mxml" >
<mx:Text text="Hello, World!" />
</mx:WindowedApplication>
.exタグは練習問題の説明を示します。.hで始まるものはヒントとし、その後にヒント番号を続けます。.sは中間ヒントソリューションとし、同様に番号が続きます。.finalタグは完成したソリューションを示します。
Pythonプログラムを作成して、この言語をFlexアプリケーションが理解しやすいXMLなどの言語に変換することもできましたが、そうすると中間の手順でソリューションのインタラクティビティが失われていたでしょう。ExercisePresenterコンポーネントではAIRを使用してテキストファイルの検索と読み取りを行い、正規表現を使用してディレクティブに分割します。
package ProgrammedLearning {
import mx.containers.Accordion;
import mx.containers.VBox;
import mx.controls.TextArea;
import mx.controls.Alert;
import flash.filesystem.File;
import flash.filesystem.FileStream;
import flash.filesystem.FileMode;
import flash.net.FileFilter;
import flash.events.Event;
public class ExercisePresenter extends Accordion {
public var dataDirectory:String;
public var chapter:String;
public var fileName:String;
private var file:File = File.documentsDirectory;
private var pathsResolved:Boolean = false;
// Called when properties are set:
protected override function commitProperties():void {
super.commitProperties();
// Prevent multiple calls:
if(!pathsResolved) {
pathsResolved = true;
resolvePaths([dataDirectory, chapter, fileName]);
}
}
private function resolvePaths(paths:Array):void {
for each(var path:String in paths) {
var successfullyResolved:File = file; // Store as far as we've gotten
file = file.resolve(path);
if(file.exists)
continue;
else { // Files installed elsewhere
var exFilter:FileFilter = new FileFilter("Exercise", "Exercise*.txt");
file = successfullyResolved;
file.browseForOpen("File Not Found; Select File to Open", [exFilter]);
file.addEventListener(Event.SELECT, parseFile);
}
return; // Wait for user to choose new path
}
parseFile();
}
private function parseFile(event:Event=null): void {
var stream:FileStream = new FileStream();
stream.open(file, FileMode.READ);
var str:String = stream.readUTFBytes(stream.bytesAvailable);
stream.close();
var entries:Array = [];
// Each entry is delimited by a '.' at the start of a line:
for each(var s:String in str.split(new RegExp("\\n\\.", "s")))
if(s.length > 0)
entries.push(s);
for each(s in entries) {
// Split on first newline:
// (Could do this with a regular expression, too!)
var brk:uint = s.indexOf("\n");
var tag:String = s.substring(0, brk);
var contents:String = s.substr(brk);
// Strip leading newlines:
while(contents.charAt(0) == '\n')
contents = contents.substr(1);
switch(tag.charAt(0)) {
case 'e':
addStep("Exercise", contents);
break;
case 'h':
addStep("Hint " + tag.substr(1), contents);
break;
case 's':
addStep("Hint solution " + tag.substr(1), contents);
break
case 'f':
addStep("Completed solution", contents);
break;
default:
}
}
}
public function addStep(label:String, contents:String):void {
// Create a new "fold" in the Accordion:
var vbox:VBox = new VBox();
vbox.percentHeight = vbox.percentWidth = 100;
vbox.label = label;
var text:TextArea = new TextArea();
text.percentHeight = text.percentWidth = 100;
text.text = contents;
vbox.addChild(text);
addChild(vbox); // Add to self (Accordion object)
}
}
}
dataDirectory、chapter、およびfileNameの各フィールドがコンポーネントに例の検索場所を指示します。これらのフィールドはパブリックで、設定されるのを待たなければ使用できない(待たないとデータが不正確になる)ため、フレームワークで自動的に呼び出されるcommitProperties()メソッドをオーバーライドします。commitProperties()は複数回呼び出されることが多いため、標準的にはフラグを使用してタスクを実行したかどうかを追跡します。この例ではresolvePaths()を1回だけ呼び出します。
resolvePaths()は一連のディレクトリ配列を反復し、それぞれが正しいことを検証します。配列の終わりに達すると、parseFile()が直接呼び出されますが、失敗した場合、ユーザが目的の場所にファイルをインストールしていない、などの理由でパス情報が正しくないということになるため、ユーザはここでディレクトリとファイルを選択できます。browseForOpen()でネイティブOSのブラウザウィンドウが開かれます。ユーザが最初からやり直しをしなくても済むように、前回正常に見つかったディレクトリが保存されていて、それが使用されます。
browseForOpen()を呼び出すのは別のスレッドを始めるようなものです(ただし、FlexとFlashではプログラマースレッドはサポートされていません。スレッドのプログラミングを正しく行うのはほとんど不可能なので、これは良いことです。ただし、Flash VMの新しいベータ版では、複数のコアの高速に使用するため、スレッドを内部使用しています)。ユーザが新しいファイルを選択したら、parseFile()を呼び出します。このために、コールバックを効果的に確立できるイベントリスナーを設定します。Flexコードのネットワークプログラミングでは特に、制御を渡してからイベントリスナーをコールバックに使用するというプログラミングを多く見かけます。
parseFile()の最初の数行は、テキストファイルを開いて読み取る標準的な方法を示しますが、これはAIRアプリケーションでしか使用できません。Javaに多少似ていますが、Javaのようにデコレータパターンが(誤って)使用されていないので、それほど冗長ではありません。
ファイルの読み取りが終ると、正規表現がこれを「エントリ」に分割します。それぞれのエントリにはドットコマンドとそれに続くテキストが含まれています(空白のエントリは破棄されます)。正規表現はJavaと同じ構文を使用します。特に単一のバックスラッシュを使用したい場合には、二重バックスラッシュを使用します。マニュアルやサンプルが不足している(少なくとも見つけられなかった)ので、ActionScriptの正規表現の使用方法を理解するまでに少し時間がかかりました。『Thinking in Java』の「Strings」の章の正規表現の項目を使用した方が役立つかもしれません。
各エントリがドットコマンドと本文テキストに分割されると、switchステートメントはaddStep()を呼び出すことによって、Accordionコンポーネントに各手順をウィンドウとして追加します。
コンポーネントをテストするため、これをMXMLで作成および構成します。
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="/2006/mxml" layout="absolute"
xmlns:ProgrammedLearning="ProgrammedLearning.*">
<ProgrammedLearning:ExercisePresenter width="100%" height="100%"
dataDirectory="FlexJamProgrammedLearning" chapter="1.
Basics" fileName="Exercise1.txt"/>
</mx:WindowedApplication>
ただし、これはソリューション全体ではありません。包含アプリケーション(最終的に作成する予定)で、すべての練習問題で構成されるメインページを作成し、各練習問題を選択するとプログラム学習Accordionが表示されるようにします。このためにはランタイムにコンポーネントを操作できる必要があります。自分のデザインオプションとFlexの構造を使用すれば、この操作は可能です。
package ProgrammedLearning {
public class DynamicTest extends ExercisePresenter {
function DynamicTest() {
percentHeight = percentWidth = 100;
dataDirectory = "FlexJamProgrammedLearning";
chapter = "1. Basics";
fileName = "Exercise1.txt";
}
}
}
今後のバージョンのFlexに期待したいのは、文字列操作ツールの向上です。この操作に最適なモデルはPython文字列ライブラリです。このライブラリは試用およびテスト済みで、ソースコードも揃っています。そのため、ActionScript文字列ライブラリの作成は、変換するだけの作業となるでしょう。
Bruce Eckelはコンピュータプログラミングに関する多数の著書や記事の執筆を手がけています。コンピュータプログラマを対象とする講義やセミナーをしばしば実施するほか、ANSI/ISO C++標準化委員会の創設にも携わりました。オブジェクト指向のプログラミング経験がないプログラマ向けの著書、『Thinking in Java』と『Thinking in C++』が代表作として知られ、JavaおよびC++言語の入門書として高い評価を得ています。どちらも無料ダウンロード*として一般に公開されてきましたが、最新の『Thinking in Java, Fourth Edition』*は無料での提供を行っていません。