サイトトップ

Director Flash 書籍 業務内容 プロフィール

Macromedia Flash非公式テクニカルノート

flashx.textLayoutパッケージのクラスを使うとコンストラクタからStageにアクセスできない

ID: FN1005002 Product: Flash CS5 and above Platform: All Version: 10 and above/ActionScript 3.0

問題
Flash Professional CS5/Flash Player 10で確認された問題です。クラス定義でflashx.textLayoutパッケージのクラスを使ったとき、コンストラクタメソッドからDisplayObject.stageプロパティでStageオブジェクトにアクセスできません。つぎのようなエラー#1009が起こります[*1]

TypeError: Error #1009: null のオブジェクト参照のプロパティまたはメソッドにアクセスすることはできません。
  at TLFNewsLayout()

[*1] かつて、[ヘルプ]の「Text Layout Framework の例:ニュースレイアウト」に紹介されたクラスTLFNewsLayoutを試すと、このエラーが生じました。英語版[Help]のコメントで問題を指摘したところ、最新の[ヘルプ]で後述の「対処法」が施されました。


原因
クラス定義でflashx.textLayoutパッケージのクラスを使ったとき、コンストラクタメソッドからDisplayObject.stageプロパティを調べると、[ドキュメントクラス]に設定していてもnullが返されます。たとえば、以下のクラスTextLayoutFormatTestを[ドキュメントクラス]に設定すると、つぎのように[出力]されます。

constructor,null,null

// ActionScript 3.0クラス定義ファイル: TextLayoutFormatTest.as
package {
  import flash.display.Sprite;
  import flashx.textLayout.formats.TextLayoutFormat;
  public class TextLayoutFormatTest extends Sprite {
    public function TextLayoutFormatTest() {
      var my_fmt:TextLayoutFormat;   // この行をコメントアウトするとアクセスできる
      trace("constructor", stage, parent);
    }
  }
}

上記のスクリプトで、TextLayoutFormatクラスで型指定した変数の宣言をコメントアウトすると、DisplayObject.stageプロパティはStageオブジェクトの参照を正しく返しします。flashx.textLayoutパッケージのクラスを使うと、コンストラクタの呼出し時には、まだTextLayoutFormatTestクラスのインスタンスがStageオブジェクトを頂点とする表示リストに加わっていないようです。


対処法
DisplayObject.addedToStageイベント(定数Event.ADDED_TO_STAGE)にリスナーメソッドを加え、そのメソッド内からDisplayObject.stageプロパティにアクセスすればStageオブジェクトの参照を得ることができます[*2]。改めてクラスTextLayoutFormatTestを定義し直したのが、つぎのスクリプト001です。

スクリプト001■DisplayObject.addedToStageイベントでDisplayObject.stageプロパティにアクセス
    // ActionScript 3.0クラス定義ファイル: TextLayoutFormatTest.as
  1. package {
  2.   import flash.display.DisplayObject;
  3.   import flash.display.Sprite;
  4.   import flash.events.Event;
  5.   import flashx.textLayout.formats.TextLayoutFormat;
  6.   public class TextLayoutFormatTest extends Sprite {
  7.     public function TextLayoutFormatTest() {
  8.       var my_fmt:TextLayoutFormat;
  9.       trace("constructor", stage, parent);
  10.       addEventListener(Event.ADDED_TO_STAGE, onAdded);
  11.     }
  12.     private function onAdded(eventObject:Event):void {
  13.       trace(eventObject.type, getParents());
  14.       if (parent == stage) {
  15.         trace("initialized!");
  16.       }
  17.     }
  18.     private function getParents():Vector.<DisplayObject> {
  19.       var parents:Vector.<DisplayObject> = new Vector.<DisplayObject>();
  20.       var _parent:DisplayObject = parent;
  21.       while(_parent) {
  22.         parents.push(_parent);
  23.         _parent = _parent.parent;
  24.       }
  25.       return parents;
  26.     }
  27.   }
  28. }

スクリプト001に定義したgetParents()メソッドは、クラス(TextLayoutFormatTest)のインスタンスの親を遡り、順にVectorオブジェクト(DisplayObjectベース型)に納めて返します(第18〜26行目)。このメソッドは、DisplayObject.addedToStageイベントで呼出しています(第13行目)。

[ムービープレビュー]で示される[出力]は、Flash Player 10または10.1(CS5)と10.2(CS5.5)では異なります[*3]。Flash Player 10または10.1の[出力]はつぎのとおりです。

constructor null null
addedToStage [object Loader],[object TextLayoutFormatTest__Preloader__],[object Stage]
addedToStage [object Stage]
initialized!

DisplayObject.addedToStageイベントが2度呼出されています。ひとつ目のイベントでは、[ドキュメントクラス]はまだStageオブジェクトの表示リストに直接加えられておらず、プリローダ([object TextLayoutFormatTest__Preloader__])とLoaderインスタンスが間に入っています。ふたつ目の呼出しで、[ドキュメントクラス]のインスタンスはStageオブジェクト直下の子になるようです。

そこで、DisplayObject.addedToStageイベントのリスナーメソッド(onAdded())は、スクリプト001第14行目で親(DisplayObject.parentプロパティ)がStageオブジェクトかどうかを確かめて、Text Layout Frameworkの初期化が終わったと判定しています。

他方、Flash Player 10.2の[出力]はつぎのとおりです。DisplayObject.addedToStageイベントは1度しか呼出されません。

constructor null null
addedToStage [object Stage]
initialized!

このDisplayObject.addedToStageイベントでクラスのインスタンスはすでに直接Stageオブジェクトの子になっていますので、リスナーメソッドの判定(スクリプト第14行目)はやはり適切に行われます。

[*2] 注[*1]に述べたとおり「Text Layout Framework の例:ニュースレイアウト」のクラスTLFNewsLayoutは、すでに対処法が施されています。けれど、問題を指摘したドキュメントとして、DisplayObject.addedToStageイベントにリスナーメソッドを加えたスクリプト002は記録および参考として残しておくことにします(サンプルSWF)。

スクリプト002■DisplayObject.addedToStageイベントのリスナーメソッドを加えたクラスTLFNewsLayout

// ActionScript 2.0クラス定義ファイル: TLFNewsLayout.as
// [ドキュメントクラス]に設定
package {
  import flash.display.Sprite;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.events.Event;
  import flash.geom.Rectangle;

  import flashx.textLayout.compose.StandardFlowComposer;
  import flashx.textLayout.container.ContainerController;
  import flashx.textLayout.container.ScrollPolicy;
  import flashx.textLayout.conversion.TextConverter;
  import flashx.textLayout.elements.TextFlow;
  import flashx.textLayout.formats.TextLayoutFormat;

  public class TLFNewsLayout extends Sprite {
    private var hTextFlow:TextFlow;
    private var headContainer:Sprite;
    private var headlineController:ContainerController;
    private var hContainerFormat:TextLayoutFormat;

    private var bTextFlow:TextFlow;
    private var bodyTextContainer:Sprite;
    private var bodyController:ContainerController;
    private var bodyTextContainerFormat:TextLayoutFormat;

    private const headlineMarkup:String = "<flow:TextFlow xmlns:flow='http://ns.adobe.com/textLayout/2008'><flow:p textAlign='center'><flow:span fontFamily='Helvetica' fontSize='18'>TLF News Layout Example</flow:span><flow:br/><flow:span fontFamily='Helvetica' fontSize='14'>This example formats text like a newspaper page with a headline, a subtitle, and multiple columns</flow:span></flow:p></flow:TextFlow>";

    private const bodyMarkup:String = "<flow:TextFlow xmlns:flow='http://ns.adobe.com/textLayout/2008' fontSize='12' textIndent='10' marginBottom='15' paddingTop='4' paddingLeft='4'><flow:p marginBottom='inherit'><flow:span>There are many </flow:span><flow:span fontStyle='italic'>such</flow:span><flow:span> lime-kilns in that tract of country, for the purpose of burning the white marble which composes a large part of the substance of the hills. Some of them, built years ago, and long deserted, with weeds growing in the vacant round of the interior, which is open to the sky, and grass and wild-flowers rooting themselves into the chinks of the stones, look already like relics of antiquity, and may yet be overspread with the lichens of centuries to come. Others, where the lime-burner still feeds his daily and nightlong fire, afford points of interest to the wanderer among the hills, who seats himself on a log of wood or a fragment of marble, to hold a chat with the solitary man. It is a lonesome, and, when the character is inclined to thought, may be an intensely thoughtful occupation; as it proved in the case of Ethan Brand, who had mused to such strange purpose, in days gone by, while the fire in this very kiln was burning.</flow:span></flow:p><flow:p marginBottom='inherit'><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Within the furnace were seen the curling and riotous flames, and the burning marble, almost molten with the intensity of heat; while without, the reflection of the fire quivered on the dark intricacy of the surrounding forest, and showed in the foreground a bright and ruddy little picture of the hut, the spring beside its door, the athletic and coal-begrimed figure of the lime-burner, and the half-frightened child, shrinking into the protection of his father's shadow. And when again the iron door was closed, then reappeared the tender light of the half-full moon, which vainly strove to trace out the indistinct shapes of the neighboring mountains; and, in the upper sky, there was a flitting congregation of clouds, still faintly tinged with the rosy sunset, though thus far down into the valley the sunshine had vanished long and long ago.</flow:span></flow:p></flow:TextFlow>";

    public function TLFNewsLayout() {
      addEventListener(Event.ADDED_TO_STAGE,initialize);
    }
    
    private function initialize(eventObject:Event):void {
      removeEventListener(Event.ADDED_TO_STAGE,initialize);
      stage.scaleMode = StageScaleMode.NO_SCALE;
      stage.align = StageAlign.TOP_LEFT;

      // Headline text flow and flow composer
      hTextFlow = TextConverter.importToFlow(headlineMarkup,TextConverter.TEXT_LAYOUT_FORMAT);
      hTextFlow.flowComposer = new StandardFlowComposer();

      // initialize the headline container and controller objects
      headContainer = new Sprite();
      headlineController = new ContainerController(headContainer);
      headlineController.verticalScrollPolicy = ScrollPolicy.OFF;
      hContainerFormat = new TextLayoutFormat();
      hContainerFormat.paddingTop = 4;
      hContainerFormat.paddingRight = 4;
      hContainerFormat.paddingBottom = 4;
      hContainerFormat.paddingLeft = 4;

      headlineController.format = hContainerFormat;
      hTextFlow.flowComposer.addController(headlineController);
      addChild(headContainer);
      stage.addEventListener(flash.events.Event.RESIZE,resizeHandler);

      // Body text TextFlow and flow composer
      bTextFlow = TextConverter.importToFlow(bodyMarkup,TextConverter.TEXT_LAYOUT_FORMAT);
      bTextFlow.flowComposer = new StandardFlowComposer();

      // The body text container is below, and has three columns
      bodyTextContainer = new Sprite();
      bodyController = new ContainerController(bodyTextContainer);
      bodyTextContainerFormat = new TextLayoutFormat();
      bodyTextContainerFormat.columnCount = 3;
      bodyTextContainerFormat.columnGap = 30;

      bodyController.format = bodyTextContainerFormat;
      bTextFlow.flowComposer.addController(bodyController);
      addChild(bodyTextContainer);
      resizeHandler(null);
    }

    private function resizeHandler(event:Event):void {
      const verticalGap:Number = 25;
      const stagePadding:Number = 16;
      var stageWidth:Number = stage.stageWidth - stagePadding;
      var stageHeight:Number = stage.stageHeight - stagePadding;
      var headlineWidth:Number = stageWidth;
      var headlineContainerHeight:Number = stageHeight;

      // Initial compose to get height of headline after resize
      headlineController.setCompositionSize(headlineWidth,headlineContainerHeight);
      hTextFlow.flowComposer.compose();
      var rect:Rectangle = headlineController.getContentBounds();
      headlineContainerHeight = rect.height;

      // Resize and place headline text container
      // Call setCompositionSize() again with updated headline height
      headlineController.setCompositionSize(headlineWidth,headlineContainerHeight);
      headlineController.container.x = stagePadding / 2;
      headlineController.container.y = stagePadding / 2;
      hTextFlow.flowComposer.updateAllControllers();

      // Resize and place body text container ;
      var bodyContainerHeight:Number = stageHeight - verticalGap - headlineContainerHeight;
      bodyController.format = bodyTextContainerFormat;
      bodyController.setCompositionSize(stageWidth,bodyContainerHeight);
      bodyController.container.x = stagePadding / 2;
      bodyController.container.y = stagePadding / 2 + headlineContainerHeight + verticalGap;
      bTextFlow.flowComposer.updateAllControllers();
    }
  }
}

図001■「Text Layout Framework の例:ニュースレイアウト」のクラスTLFNewsLayoutが作成したレイアウト
図001左図

[*3] Flash PlayerのバージョンによりText Layout Frameworkの初期化の過程が異なることについては、「TLFテキストが配置されたメインタイムラインの初期化」をご参照ください。


作成者: 野中文雄
更新日 2012年1月3日 「対処法」でFlash Player 10.2(Flash Professional CS5.5)における動作の違いを加え、スクリプト001も改善した。
更新日: 2010年6月20日 注[*1]と[*2]を追加。
作成日: 2010年5月24日


Copyright © 2001-2012 Fumio Nonaka.  All rights reserved.