Platform: All
Version: Flash 8
ScrollPaneコンポーネントは、ScrollPane.contentPathプロパティに、外部からロードするファイルのURLまたはMovieClipシンボルのリンケージ識別子が設定できます。ScrollPaneを始めとするクラス定義を確認して、この処理の流れを概観してみたいと思います。
1. ScrollPaneクラスの処理
まず、mx.containers.ScrollPaneクラス(スクリプト001)を見ると、contentPathはgetter/setterメソッドであることがわかります。インスタンスプロパティinitializingはtrueでインライン初期化されているため、setterメソッドcontentPath()の最初のif条件がfalseと評価されるので、ifブロック内は処理されず、[プロパティ]インスペクタの[パラメータ]でcontentPathに設定された値は内部プロパティ__scrollContentに格納されます。
スクリプト001■mx.containers.ScrollPaneクラスの定義
import mx.core.ScrollView;
class mx.containers.ScrollPane extends ScrollView {
// ...[中略]...
var initializing:Boolean = true;
// ...[中略]...
var __scrollContent:String;
// ...[中略]...
function set contentPath(scrollableContent:String) {
if (!initializing) {
if (scrollableContent == undefined) {
// ...[中略]...
} else {
// ...[中略]...
createChild(scrollableContent, "spContentHolder");
}
}
__scrollContent = scrollableContent;
}
// ...[中略]...
function createChildren(Void):Void {
super.createChildren();
// ...[中略]...
initializing = false;
if (__scrollContent != undefined && __scrollContent != "") {
contentPath = __scrollContent;
}
}
// ...[後略]...
}
|
つぎに、ScrollPaneコンポーネントは最終的に(ScrollPane > ScrollView > View > UIComponent > UIObject)UIObjectを継承しますので、サブオブジェクトを作成するタイミングでcreateChildren()メソッドが呼出されます。ScrollPane.createChildren()メソッドは、initializingプロパティをtrueに設定したうえで、内部プロパティ__scrollContentに格納されている値をcontentPathに設定しています。すると、改めてgetterメソッドcontentPath()が__scrollContentの値を引数として呼出されます。
今度はinitializingプロパティがtrueですので、ifステートメントのブロック内の処理が行われます。つまり、__scrollContentに値が設定されていれば、その値と"spContentHolder"という文字列を引数として、createChild()メソッドを呼出します。
2. ScrollViewクラスの処理
createChild()メソッドは、mx.containers.ScrollPaneクラスが継承するmx.core.ScrollViewクラスに定義されています(スクリプト002)。もっとも、このScrollView.createChild()メソッドは、さらにそのスーパークラスであるmx.core.ViewのcreateChild()メソッドを呼出し、その戻り値のMovieClipを返しているだけです。
スクリプト002■mx.core.ScrollViewクラスの定義
import mx.core.View;
class mx.core.ScrollView extends View {
function createChild(id, name:String, props:Object):MovieClip {
var newObj:MovieClip = super.createChild(id, name, props);
return newObj;
}
}
|
ScrollPaneクラスからは、引数としてScrollPane.contentPath(内部的には__scrollContentプロパティ)の値と文字列"spContentHolder"のふたつしか受取っていませんので、第3引数はなし(undefined)ということになります。したがって、ViewのcreateChild()メソッドにも、そのふたつの引数がそのまま渡されます。
3. Viewクラスの処理
mx.core.ScrollViewクラスの継承するmx.core.Viewクラスが、ようやくデータをロードする中心的な処理を扱います(スクリプト003)。サブクラスのScrollViewから呼出されたView.createChild()がそのためのメソッドで、[ライブラリ]からMovieClipシンボルのインスタンスを作成するのか、あるいは外部ファイルをロードするのかという仕分けも行います。
スクリプト003■mx.core.Viewクラスの定義
import mx.events.UIEventDispatcher;
import mx.core.UIObject;
import mx.core.UIComponent;
class mx.core.View extends UIComponent {
// ...[中略]...
var depth:Number;
// ...[中略]...
var loadExternal:Function;
// ...[中略]...
private var _loadExternalClass:String = "UIComponent";
// ...[中略]...
function createChild(className, instanceName:String, initProps:Object):MovieClip {
if (depth == undefined) {
depth = 1;
}
var newObj:MovieClip;
if (typeof (className) == "string") {
newObj = createObject(className, instanceName, depth++, initProps);
} else {
newObj = createClassObject(className, instanceName, depth++, initProps);
}
if (newObj == undefined) {
newObj = loadExternal(className, _loadExternalClass, instanceName, depth++, initProps);
}
// ...[中略]...
return newObj;
}
// ...[中略]...
static function extension() {
mx.core.ExternalContent.enableExternalContent();
}
}
|
ScrollPaneから呼出されるcreateChild()メソッドには、第1引数としてScrollPane.contentPath(内部的には__scrollContentプロパティ)の文字列が渡されます。createChild()メソッドは、まずその文字列をリンケージ識別子として、UIObject.createObject()メソッドを呼出します。
UIObject.createObject()メソッドは、指定されたリンケージ識別子のMovieClipシンボルを[ライブラリ]から探し、そのMovieClipインスタンスを生成します。MovieClipインスタンスが作成できたら、UIObject.createObject()はインスタンスの参照を返します。第1引数に指定されたのが外部ファイルのURLだった場合、その文字列のリンケージ識別子は存在しません。すると、インスタンスも作成できませんので、戻り値はundefiendになります。
UIObject.createObject()メソッドの戻り値がundefinedだった場合には、つぎにloadExternal()メソッドを呼出します。loadExternal()は、外部ファイルのロードを行うメソッドです。つまり、createChild()メソッドは、まず第1引数をリンケージ識別子とみなして[ライブラリ]からMovieClipインスタンスを作成しようとし、それができなかった場合はつぎに引数をURLとして扱って、外部ファイルをロードするという処理の流れになります。
4. ExternalContentクラスの処理
loadExternal()メソッドは、Viewクラスにもさらにそのスーパークラスにも、その定義が存在しません。これは、mx.core.ExternalContentクラスのメソッドなのです(スクリプト004)。ExternalContentクラスは継承によらずに、必要なメソッドをViewクラスに追加して拡張します。この手法は、「Mix-in」と呼ばれます[*1]。
実際にloadExternalなどのメソッドをViewクラスに追加しているのは、classConstruct()メソッドです。ActionScriptは、Function.prototypeプロパティにプロパティやメソッドを設定することにより、動的に拡張できることが特徴です(「ActionScript 2.0と1.0の継承について」参照)。classConstruct()メソッドは、View.prototypeオブジェクトにExternalContentクラスから必要なメソッドを設定して、Viewクラスを拡張しています。
スクリプト004■mx.core.ExternalContentクラスの定義
import mx.core.UIObject;
import mx.core.View;
class mx.core.ExternalContent {
// ...[中略]...
var createObject:Function;
// ...[中略]...
function loadExternal(url:String, placeholderClassName:String, instanceName:String, depth:Number, initProps:Object):MovieClip {
var newObj:MovieClip;
newObj = createObject(placeholderClassName, instanceName, depth, initProps);
// ...[中略]...
return newObj;
}
// ...[中略]...
static function classConstruct():Boolean {
var v = View.prototype;
var p = ExternalContent.prototype;
v.loadExternal = p.loadExternal;
// ...[中略]...
return true;
}
// ...[中略]...
static function enableExternalContent():Void {}
// ...[中略]...
static var classConstructed:Boolean = classConstruct();
// ...[後略]...
}
|
そこでまずひとつめの疑問は、classConstruct()メソッドがいつどのようにして呼ばれるかということです。実は、ExternalContentクラスがロードされさえすれば、classConstruct()は自動的に呼出されます。
ExternalContentクラスには、classConstructedという静的プロパティがあります。インライン初期化される値は、classConstruct()メソッドの戻り値です。もっとも、このプロパティの値には、何の意味もありません。classConstructedプロパティは、いわゆるダミーです。プロパティ値の設定という「かたち」を採って、classConstruct()メソッドの呼出しを行っているのです。
静的プロパティは、クラスのロード時に初期化されます。したがって、classConstruct()メソッドも、クラスのロード時に呼出されることになります。けれども、このExternalContentクラスは、Viewクラスなど他のクラスから継承されていません。ですから、ViewクラスにMix-inするためには、ExternalContentクラスをロードする仕組みが何かしら必要になる訳です。
するとつぎの疑問は、ExternalContentクラスがどこでどのようにロードされているかです。これは、かなりわかりにくい仕組みになっています。
第1に、静的メソッドExternalContent.enableExternalContent()の存在に注目しましょう。このメソッドは空っぽで、実装がありません。また、他のクラスから継承もされませんので、サブクラスが実装することもありません。これまた、静的プロパティclassConstructedと同じような、いわばダミーのメソッドなのです。
では第2に、ExternalContent.enableExternalContent()メソッドを呼出している場所です。このメソッドを呼出すステートメントは、Viewクラス(スクリプト003)の静的メソッドView.extension()のブロック内にあります。View.extension()メソッドには、ExternalContent.enableExternalContent()メソッドを呼出すステートメントが1行あるだけです。
すると第3に、View.extension()メソッドが呼出されている箇所を探したくなります。ところが、そんなステートメントは存在しません[*2]。それどころか、View.extension()メソッドのコメントには、"this never gets called"(このメソッドが呼ばれることはありません)と書かれています。疑問を解く鍵は、その後に続く"it just makes sure the external content module gets loaded"(このメソッドはExternalContentのモジュールが確実にロードされるようにしているだけです)という説明の意味にあります。
静的メソッドは、クラスがロードされれば、ただちに呼出すことができます。静的メソッドのプロック内で、他のクラスの静的メソッドを参照していると、Flashはその静的メソッドにもすぐにアクセスできるように、静的メソッドをもっているクラスも一緒にロードしてしまうのです。つまり、実際に静的メソッドView.extension()が呼出されるかどうかに関わりなく、そのメソッドブロック内で静的メソッドExternalContent.enableExternalContent()を参照しているだけで、ただちにアクセスできるようExternalContentクラスのロードも行われるということです。
[*1]「Mix-in」については、Flash 8オンラインヘルプ[Flash コンポーネントガイド] > [コンポーネントイベントの処理] > [リスナーによるイベントの処理] > [リスナーオブジェクトの使用]で、EventDispatcher.addEventListener()メソッドに関連してつぎのように簡単に触れられています
addEventListener()は、どのコンポーネントインスタンスに対しても呼び出せます。これはEventDispatcherクラスからのMix-inとして、すべてのコンポーネントに備わっています。"Mix-in"とはクラスの一種で、別のクラスの動作を強化するために特定の機能を提供するものです。詳細については、『コンポーネントリファレンスガイド』のEventDispatcher.addEventListener()を参照してください。
[*2] 呼出したところで、唯一のステートメントであるExternalContent.enableExternalContent()の呼出しは、その実装が空ですから意味はありません。
|
5. 結び
本稿では、ScrollPaneクラスのScrollPane.contentPathプロパティに設定したMovieClipシンボルのリンケージ識別子や外部ファイルのURLが、どのような処理の流れでインスタンスを生成したり、外部ファイルをロードする仕組みになっているかを見てきました。なお、ExternalContent.loadExternal()メソッドは、ExternalContentクラスの他のメソッドと連携して、外部ファイルのロード待ちや進捗管理、エラーチェックなどを行っています。ただ、本稿の目的を外れますので、その具体的な実装についてはこれ以上触れないこととします。
mx.containersパッケージのクラスでは、他にもWindowクラスはScrollViewを継承しています。また、mx.controlsパッケージのAlertクラスはWindowを、LoaderクラスはViewを継承しています。したがって、これらのコンポーネントのcontentPathプロパティ(setterメソッド)もまた、同様の仕組みでコンテンツのロードを行っていると考えることができます。
_____
作成者: 野中文雄
作成日: 2006年5月11日