サイトトップ

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

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

Alert.show()をComboBox.changeイベントで呼出すと無限ループになる

ID: FN0606001 Product: Flash

Platform: Windows
Version: 8.0

問題
Flash 8で報告された問題です。Alert.show()メソッドをComboBox.changeイベントに設定したコールバック関数内から呼出すと、つぎのようなエラーを発生することがあります。

1 つのアクション内で256レベルの反復を超えました。
無限ループの可能性があります。
このムービーではこれ以上のアクションの実行は無効になります。

問題を再現する手順は、つぎのとおりです[*1]

  1. [ライブラリ]にAlertとComboBoxインスタンスを格納します。
  2. ComboBoxインスタンスをメインタイムラインに配置し、インスタンス名をmyComboBoxとし、[list]パラメータにドロップダウンリストの項目をふたつ以上設定します(図001)。
  3. メインタイムラインのフレームアクションに、以下のスクリプト001を設定します。
  4. [ムービープレビュー]を行い、ComboBoxインスタンスの項目を変更すると、上記のような256レベルの再帰を超えた旨のエラーが表示されます。

図001■ComboBoxインスタンスの[パラメータ]設定と[ライブラリ]パネル
ComboBox

スクリプト001■ComboBoxインスタンスの項目を選択するとAlertインスタンスを表示

// タイムライン: _root
// フレームアクション
import mx.controls.Alert;
import mx.controls.ComboBox;
var oListener:Object = new Object();
oListener.change = function(eventObject:Object):Void {
  trace(eventObject.target);
  Alert.show("message"); // 256レベル超再帰エラー!
};
myComboBox.addEventListener("change", oListener);

上記スクリプト001のComboBox.changeイベントハンドラメソッドには、イベントの発生したインスタンス(イベントオブジェクトのtargetプロパティ)をtrace()するステートメントが挿入してありますので、ComboBox.changeイベントが何度も繰返し呼出されていることが確認できます。

[*1] タイムラインにインスタンスを配置せず、スクリプトだけでテストしたい場合には、[ライブラリ]にAlertとComboBoxコンポーネントを格納したうえで、つぎのスクリプト002をフレームアクションに設定します。

スクリプト002■ComboBoxインスタンスを動的に生成してその項目を選択したときAlertインスタンスを表示

// タイムライン: _root
// フレームアクション
import mx.controls.Alert;
import mx.controls.ComboBox;
var myComboBox:ComboBox =
this.createClassChildAtDepth(ComboBox, DepthManager.kTop);
myComboBox.dataProvider = ["item0", "item1", "item2"];
myComboBox.move(10, 10);
var oListener:Object = new Object();
oListener.change = function(eventObject:Object):Void {
  trace(eventObject.target);
  Alert.show("message"); // 256レベル超再帰エラー!
};
myComboBox.addEventListener("change", oListener);

原因
Flash 8のバグだと思われます。Flash MX 2004では、この現象は確認されていません。Flash 8からFlash Player 7書出しをしても、問題は発生します。したがって、Flash 8に実装されている、AlertまたはComboBoxを構成するクラスに、バグがあるものと推測されます(後述「対処法」注[*2]をご参照ください)。

対処法
対処法を、3つご紹介します[*2]

[1]Alert.show()メソッドの呼出しを遅らせる
ComboBox.changeイベントハンドラメソッド中でAlert.show()メソッドを呼出すと、イベントの呼出しが再帰してしまうようです。したがって、若干のタイムラグを与えてから、Alert.show()メソッドを呼出します。

つぎのスクリプト003は、Flash 8で実装されたsetTimeout()関数を用いて、100ミリ秒後にAlert.show()メソッドを呼出しています[*3]

スクリプト003■setTimeout()関数でAlert.show()メソッドの呼出しを遅らせる

// タイムライン: _root
// フレームアクション
import mx.controls.Alert;
import mx.controls.ComboBox;
var oListener:Object = new Object();
oListener.change = function(eventObject:Object):Void {
  // Alert.show("message");
  setTimeout(Alert.show, 100, "message");
};
myComboBox.addEventListener("change", oListener);

[2]Alert.show()メソッドを呼出す間リスナーオブジェクトを外す
Alert.show()メソッドを呼出したときにComboBox.changeイベントが再呼出しされてしまうようですので、呼出す前にリスナーオブジェクトをComboBoxインスタンスから一旦外し、呼出し後に再設定することで再帰が避けられます

スクリプト004は、Alert.show()メソッドを呼出す前に、ComboBoxインスタンスからEventDispatcher.removeEventListener()メソッドでリスナーオブジェクトを一旦除き、呼出し後にEventDispatcher.addEventListener()メソッドでオブジェクトを再度リスナー登録しています。

スクリプト004■Alert.show()メソッドを呼出す間リスナーオブジェクトを外す

// タイムライン: _root
// フレームアクション
import mx.controls.Alert;
import mx.controls.ComboBox;
var oListener:Object = new Object();
oListener.change = function(eventObject:Object):Void {
  var _cb:ComboBox = eventObject.target;
  _cb.removeEventListener("change", this);
  Alert.show("message");
  _cb.addEventListener("change", this);
};
myComboBox.addEventListener("change", oListener);

[3]Alert.show()メソッドを呼出す前にComboBoxインスタンスからフォーカスを外す
ComboBoxインスタンスからフォーカスを外すことによっても、問題は回避できるようです。Aletウィンドウを表示する際には、ComboBoxにフォーカスを保持する必要はありません。スクリプト005は、Alert.show()メソッドを呼出す前に、Selection.setFocus()メソッドでComboBoxインスタンスからフォカスを外しています。

スクリプト005■Alert.show()メソッドを呼出す前にフォーカスを外す

// タイムライン: _root
// フレームアクション
import mx.controls.Alert;
import mx.controls.ComboBox;
var oListener:Object = new Object();
oListener.change = function(eventObject:Object):Void {
  Selection.setFocus(null);
  Alert.show("message");
};
myComboBox.addEventListener("change", oListener);


[*2] Alertクラスを修正すると、一応問題は回避することができます。

静的メソッドAlert.show()functionブロック内で、PopUpManager.createPopUp()メソッドに渡すオブジェクトから、validateNowプロパティを削除(コメントアウト)します(スクリプト006)。すると、ComboBox.changeイベントハンドラメソッド内からAlert.show()メッセージを呼出しても、エラーは発生しなくなります。

スクリプト006■Alertクラス

// Alertクラス
class mx.controls.Alert extends Window
{
  // ...[中略]...
  static function show(text,title,flags,parent,listener,icon,defButton):Alert
  {

    var o = new Object();
    // ...[中略]...
    o.validateNow = true;  //←コメントアウトでエラー解消
    var m = PopUpManager.createPopUp(parent,Alert, modal, o);
    // ...[中略]...
    m.addEventListener("click", listener);
    return m;
  }

もっともこの修正が、Alertクラスだけでなく、PopUpManagerその他のクラスとの連携に与える影響を確実に把握しないかぎり、クラスに直接手を加えることには慎重になるべきでしょう。

[*3] setInterval()関数やMovieClip.onEnterFrameイベントハンドラメソッドを使って、呼出しを遅らせても結構です。

_____

作成者: 野中文雄
協力者: youich
作成日: 2006年6月29日


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