AIR/Flexの最近のブログ記事

ActionScript3 で RSS リーダーを作成したのだが、ある特定のサイトの RSS フィードを受信すると、

var feed:IFeed = FeedFactory.getFeedByString(loader.data);
var items:Array = feed.items;
for each(var item:IItem in items){
    RssTitle.push(item.title);
    RssLink.push(item.link);
    RssDate.push(item.date);
}

と、item.date を参照しているところで、

Error: Unable to parse the string [2009-03-11T112033] into a date. The internal error was: Error: This date does not conform to W3CDTF.

というエラーを出して落ちる。

世の中に出回っている他の RSS リーダーでは問題なく処理されているフィードなので、こちらのプログラムに問題があるのかと悩んだが、どうも、メッセージどおりに、このフィードの作成日付(例:<dc:date>2009-03-11T112033</dc:date>)が正しく W3CDTF 形式になっていないのが悪いと結論づけていいようだ。

W3C のサイトで W3CDTF 形式をチェックしたが、
http://www.w3.org/TR/NOTE-datetime

   Year:
      YYYY (eg 1997)
   Year and month:
      YYYY-MM (eg 1997-07)
   Complete date:
      YYYY-MM-DD (eg 1997-07-16)
   Complete date plus hours and minutes:
      YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
   Complete date plus hours, minutes and seconds:
      YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
   Complete date plus hours, minutes, seconds and a decimal fraction of a second:
      YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)

ということで、明らかに「2009-03-11T112033」という書き方は間違いだ。正しくは、「2009-03-11T11:20:33+09:00」だね。

ということで、当方で作成した RSS リーダー側の挙動で基本的に問題はないようだが、他の RSS リーダーで上記のような「中途半端に W3CDTF 形式に似た形式」を処理してるのに、うちのプログラムでは落ちるというのも仄かな悔しさが漂うなあ・・・(^^;

取りあえず、今回の形式だけは、DateUtil.as を修正して「強引に」対応しておいた。
(具体的には、DateUtil.as 中の parseW3CDTF 関数に、以下の記述を追加)

var nonColTime:RegExp   = /^(\d{2})(\d{2})(\d{2})/s;
var matchText:Object    = nonColTime.exec(offsetStr);
if (matchText != null) {
    timeStr  = offsetStr;
    hour     = matchText[1];
    minutes = matchText[2];
    seconds = matchText[3];
    offsetHours = 0;
    offsetMinutes = 0;
}

あんまり良い手だとも思わないけど、取りあえずね、取りあえず。

AIR1.jpg

透過した AIR アプリを作る方法。

■ADFファイル

<initialWindow> タグ内の、<systemChrome>タグと<transparent>タグを以下のように設定。
<systemChrome>none</systemChrome>
<transparent>true</transparent>

※<transparent>を true に設定したら、<systemChrome>は必ず none に設定しないとエラーになる。

■MXMLファイル

<mx:WindowedApplication>タグ内で、backgroundAlpha属性を"0.0"("0"でも可)に設定。これで背景が透ける。(alpha属性はアプリ全体の透過設定なので、"0"にするとAIRアプリがまったく表示されない。透過させるのは背景のみだからbackgroundAlpha属性を設定ね)

一応、例をのせとく。

<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
 layout="absolute" width="151" height="245"
 mouseDown="stage.nativeWindow.startMove();"
 backgroundAlpha="0.0"
 showStatusBar="false"
 showTitleBar="false"
 borderStyle="solid" borderColor="#151921">

この例では、 showStatusBar="false"、showTitleBar="false"の設定で、タイトルバーなどを消しているので、アプリを掴んで動かすことが出来なくなる。そこで、アプリ上でマウスのボタンが押されたら、Window の移動処理(ドラッグ)がスタートするよう、mouseDown="stage.nativeWindow.startMove();"の設定を行っている。

で、こういう設定をした上で、透過PNGや透過GIFファイルをイメージとして配置すれば、透けた AIR アプリのできあがりである。

分かり易いようにボーダー表示をした状態で、Vista のガジェットの上に置いてみた。
透けてますなあ~(笑)

値のセットされていない配列をチェックしたかったので、

if (!a[i].exists) {

とかやってたら、ホントは

if (a[i] == undefined) {

だった。

はまった・・・

ファイルの存在有無は、

var file:File = File.desktopDirectory.resolvePath(ファイルパス);
// ファイルが存在しているか?
if (file.exists) {
    trace("存在してる!");
}
else {
    trace("存在してない!");
}

で、いいんだけどね。

初めて ActionScript を組むプログラマに説明するために、以前「複数の文字列を延々スクロール」というエントリに貼り付けてたソースを Flex Builder に持ってきて説明しようとしたら・・・

「1084: シンタックスエラー : rightbrace が 0 の前に必要です。」

というエラーが出やがんの。

'}' の数が合ってないってことだろうなあ。いやいや、'}' の数は合ってるぞ。
そういえば以前、化け文字がソースの途中に入ってて、こんなエラーになったことがあったなあ・・・と見てみたけど、やっぱおかしいところはないぞ・・・

その場では結局何が悪いのかわからず、別のサンプルを使って説明。

実は、つまらないミスでした。
該当の行がこれ。

TextIndex   = 0;    # 最初の配列

そう。
コメントの始まりは、'#' じゃなく '//'。

TextIndex   = 0;    // 最初の配列

これでOK。

いやぁ、日頃、自分でプログラミングするのはほとんど Perl ばかりなので、ついつい・・・

C とか Java とか VB なんかをメインとしているプログラマにはあり得ないミスでしたな。面目ない。(^^;

今回は、簡単なスライドショーガジェットを作ってみた。
3枚の画像を10秒毎に切り替えて表示という処理を延々繰り返す。
この間作った電光掲示板もどきより制御も簡単で、特に目新しいもの無し。

一点。
タイマーイベントの一発目の処理は、指定しているインターバルタイムが最初に経過した後なので、タイマーイベントを開始する前に、一発目の処理を実行しておく必要あり。
今回の場合であれば、1枚目の絵のパスセット。
こうしないと、最初の 10秒間は何も表示していない状態になってしまう。


<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    width="340" height="100" color="#0B333C"
    initialize="initProc();">
    <mx:Image x="0" y="0" width="340" height="100" id="img1"/>

    <mx:Script>

        <![CDATA[

            public var FilePath:Array    = new Array();    // 画像パス
            public var FileIndex:int;    // 配列インデックス

            //============================
            // メイン処理(10秒毎に画像パスセット処理呼び出し)
            //============================
            private function initProc():void {

                setFilePath();

                FileIndex    = 0;    // 最初の画像
                img1.source    = FilePath[FileIndex];    // 最初のパスセット

                var timeProc1:Timer = new Timer(10000);    // 10秒毎
                timeProc1.addEventListener(TimerEvent.TIMER, setImage);
                timeProc1.start();

            }

            //============================
            // メイン処理(10秒毎に画像パスセット処理呼び出し)
            //============================
            private function setImage(event:TimerEvent):void {

                FileIndex++;

                if (FileIndex >= FilePath.length) {
                    FileIndex    = 0;
                }

                img1.source    = FilePath[FileIndex];    // パスのセット

            }

            //============================
            // 表示画像のパス設定
            //============================
            private function setFilePath():void {

                FilePath[0]
                    = "file:///M|/X/data/TEST/src/img/carp.jpg";
                FilePath[1]
                    = "file:///M|/X/data/TEST/src/img/iwakuni.jpg";
                FilePath[2]
                    = "file:///M|/X/data/TEST/src/img/metro.jpg";

            }

        ]]>
    </mx:Script>

</mx:WindowedApplication>


うーん・・・どうでもいいが、ファイルパスの相対パス設定の仕方がわからん。
ああ、これ、URL だな。
ローカルの画像ファイルを「ファイルパス」で指定して読み込んで表示するには、別のやり方をしないといけないということか。

しつこく文字列横スクロールの話。

前回までは1つの文字列を一度スクロールさせたら終わりだったが、今回は複数の文字列(配列にセット)を順番に延々とスクロールさせ続けるプログラム。

一回で終わりなら timerComplete イベントで終了処理(文字列が左端に消えていく処理)を行えばいいのだが、複数の文字列の表示を順に延々行うケースで timerComplete イベントは使えない。だって、「延々処理を繰り返す」ので TIMER_COMPLETE な状態にはならないもん。

ということで、グローバル変数でスクロール処理回数を保持し、それと文字列の表示枠の大きさとを比較して処理の終了タイミングを見たり、次の文字列に移ったとき、スクロール処理回数を初期化(0回)したりといった「泥臭い」処理を組み込んでやる。これが一番簡単。下手に複数の Timer イベントを駆使して実現しようとすると地獄を見るし、技術者的には楽しいかもしれんけど、そんなソースを後で見せられてもメンテナンスできんで。(笑)

ということで、以下がサンプル。


<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication
    xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    initialize="initProc();" width="367" height="109">

    <mx:Script>
        <![CDATA[

            // グローバル変数
            public var TextIndex:int;    // 処理中の文字列配列添字
            public var ProcCount:int;    // 処理の実行回数

            public var TextData:Array    = new Array();    // 表示文字列
            public var TextLastData:Array    = new Array();    // 消えていく文字列処理用

            // 定数
            // text1 に表示できる最大文字数
            public static const MAX_TEXT_SIZE:int    = 40;

            //================================================================
            // 初期処理(メイン)
            //================================================================
            private function initProc():void {

                setText();    // 表示文字列を配列にセット

                TextIndex    = 0;    // 最初の配列
                ProcCount    = 0;    // 文字列セット処理回数 0回

                // ひたすら表示文字列セット処理を呼び続ける
                var timeCheck1:Timer = new Timer(100);    // 100ミリ秒毎
                timeCheck1.addEventListener(TimerEvent.TIMER, setText2Label);
                timeCheck1.start();

            }

            //================================================================
            // ラベルへの表示文字列セット処理
            //================================================================
            private function setText2Label(event:TimerEvent):void {

                ProcCount++;

                if (ProcCount <= 1) {    // 初回のみ
                        TextLastData[TextIndex]    = TextData[TextIndex];
                }

                // まだ、処理回数が文字列の長さを超えてない?
                // (ちなみに、文字列が左端まで行ったところで 2秒ほど動きを
                // 停止させたいので MAX_TEXT_SIZE に +20している)
                if (ProcCount <= (MAX_TEXT_SIZE + 20)) {

                    // 時間が経過する毎に、表示文字列の前に挿入する全角スペー
                    // スの数を減らしていく
                    var blankCnt:int    = MAX_TEXT_SIZE - ProcCount;
                    if (blankCnt <= 0) {
                        label1.text    = TextData[TextIndex];
                    }
                    else {
                        label1.text    = (new Array(blankCnt).join(" ")) + TextData[TextIndex];
                    }

                }
                else {

                    // 時間が経過する毎に、表示文字列の一番前の文字を削除
                    // (文字列がだんだん消えていく動き)
                    TextLastData[TextIndex]    = TextLastData[TextIndex].substr(2);
                    label1.text    = TextLastData[TextIndex];

                    // 最後に、次の文字列処理を許可する
                    if (TextLastData[TextIndex] == '') {
                        TextIndex++;
                        ProcCount    = 0;    // 処理回数初期化

                        // 配列の最後までいったら最初に戻る
                        if (TextIndex >= TextData.length) {
                            TextIndex    = 0;
                        }

                    }

                }

            }

            //================================================================
            // 表示文字列の設定
            //================================================================
            private function setText():void {

                TextData[0]    = "わたしは宇宙からきたゴリ星人だ。";
                TextData[1]    = "あなたは衣笠似のゴリラだ。";
                TextData[2]    = "あなたはゴリラ似のガッツ石松だったのですね?";

            }

        ]]>
    </mx:Script>
    <mx:Label x="41" y="24" text="この下に文字が流れながら表示されます"/>
    <mx:Label x="41" y="50" width="279" id="label1"/>

</mx:WindowedApplication>


ちなみに、TextIndex が 0 のときに setText 処理を呼ぶようにすれば、TextLastData を使わずに直接 TextData を使えばいいので、グローバル変数も減るし、TextData の値を TextLastData にセットする処理も減るので幾分ソースもスッキリすると思うが、そこは、まあ、好き好きで。

AS_test4_2.gif

この間書いた「文字列が横スクロールする AIR アプリ」の続き。

この間は、右から現れた文字列が左端までスクロールしてくるところまで作った。今回は、Label の左端までスクロールして一旦停止後、今度はそのまま左側にスクロールしながら消えていく動きを追加。

左端までスクロールさせる timer イベントが終了した時に実行される timerComplete イベントで「その後、文字が更に左にスクロールして消えていく」処理を呼び出します。

「その後、文字が更に左にスクロールして消えていく」処理の中では、また別の timer イベントを発生させ、文字列が空になるまで処理繰り返します。
文字列が空になるまで処理を繰り返すのは、「左スクロール」の動きを、文字列の一文字目の文字を削除しては表示を繰り返すことで実現しているからじゃね。


<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication
 xmlns:mx="http://www.adobe.com/2006/mxml"
 layout="absolute"
 initialize="initProc();" width="367" height="109">

 <mx:Script>
  <![CDATA[

   // グローバル変数
   public var TextData:String = "わたしは宇宙からきたゴリ星人だ。多分、そうだ。"; // 表示文字列
   // 定数
   public static const MAX_TEXT_SIZE:int = 40; // text1 に表示できる最大文字数

   private function initProc():void {

    var timeCheck:Timer = new Timer(100, MAX_TEXT_SIZE + 10);
    timeCheck.addEventListener(TimerEvent.TIMER, onTick);
    timeCheck.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete);  // 左端まで移動した後の処理
    timeCheck.start();

   }

   // ラベルにセットする文字列の編集
   private function onTick(event:TimerEvent):void {

    // 時間が経過する毎に、表示文字列の前に挿入する全角スペースの数を減らしていく
    var blankCnt:int = MAX_TEXT_SIZE - event.target.currentCount;
    
    if (blankCnt <= 0) {
     label1.text = TextData;
    }
    else {
     label1.text = (new Array(blankCnt).join(" ")) + TextData;
    }

   }

   private function onTimerComplete(event:TimerEvent):void {

    // 文字列の文字数分だけ処理を行う
    var timeCheck2:Timer = new Timer(100, TextData.length);
    timeCheck2.addEventListener(TimerEvent.TIMER, onTick2);
    timeCheck2.start();

   }

   // ラベルにセットする文字列の編集
   private function onTick2(event:TimerEvent):void {

    // 時間が経過する毎に、表示文字列の一番前の文字を削除
    TextData = TextData.substr(2);
    // ラベルにセット
    label1.text = TextData;

   }

  ]]>
 </mx:Script>
 <mx:Label x="41" y="24" text="この下に文字が流れながら表示されます"/>
 <mx:Label x="41" y="50" width="279" id="label1"/>

</mx:WindowedApplication>


こんな感じ。

AS_test4.gif

ActionScript3 で、簡単な電子掲示板ぽい文字列横スクロールのプログラムを書いてみる。

グローバル変数を使って、Timer クラスのイベントで編集するのが簡単じゃろう。

どうしても生理的にグローバル変数が使いたくないという偏屈者は、ActionScript2 との互換性のために用意されている setInterval や setTimeout などのパブリック関数を使って引数渡しなどすることになるんだろうが、どう考えてもすっきりしたコードにはならんじゃろう。

素直にグローバル変数を使ったら、↓ほら、そこそこすっきり。

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication
 xmlns:mx="http://www.adobe.com/2006/mxml"
 layout="absolute"
 initialize="initProc();" width="367" height="109">

 <mx:Script>
  <![CDATA[

   // グローバル変数
   public var TextData:String = "わたしは宇宙からきたゴリ星人だ。多分、そうだ。"; // 表示文字列
   // 定数
   public static const MAX_TEXT_SIZE:int = 40; // text1 に表示できる最大文字数

   private function initProc():void {

    // text1 の端まで文字列を1秒毎にスクロールさせ、10秒そのまま
    var timeCheck:Timer = new Timer(1000, MAX_TEXT_SIZE + 10);
    timeCheck.addEventListener(TimerEvent.TIMER, onTick);
    timeCheck.start();

   }

   // ラベルにセットする文字列の編集
   private function onTick(event:TimerEvent):void {

    // 時間が経過する毎に、表示文字列の前に挿入する全角スペースの数を減らしていく
    var blankCnt:int = MAX_TEXT_SIZE - event.target.currentCount;
    
    if (blankCnt <= 0) {
     label1.text = TextData;
    }
    else {
     label1.text = (new Array(blankCnt).join(" ")) + TextData;
    }

   }

  ]]>
 </mx:Script>
 <mx:Label x="41" y="24" text="この下に文字が流れながら表示されます"/>
 <mx:Label x="41" y="50" width="279" id="label1"/>

</mx:WindowedApplication>

これで、「わたしは宇宙からきたゴリ星人だ。多分、そうだ。」という文字が右手から左手に流れていき、ラベルの一番左まできたら、そこで 10秒間停止・・・という動きをする。(文字列の先頭にスペースをセットし、その数を段々と減らしていくことでスクロールしているように見せている)

こういうのを、setInterval や setTimeout などを使って書くと、もっとごちゃごちゃになる予感。
素直にグローバル変数を使うのが吉。(そもそも、今時、グローバル変数を嫌う理由がない)

AIR の開発に Flex Builder 3 を使ってるんだけど、VisualBasic みたいに Timer コンポーネントは無いのね。

一定時間毎に、グローバル変数の値をチェックして、内容が変化したら処理を行いたいのだが、そういう場合は Timer クラスを使った処理を手書きせんといかんのね。

Timer を使った一番単純な例だと、こんな感じ。

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication
 xmlns:mx="http://www.adobe.com/2006/mxml"
 layout="absolute"
 initialize="initProc();">

 <mx:Script>
  <![CDATA[

   // タイマー処理開始
   private function initProc():void {

    // 5秒毎に延々とイベント送出を続けるタイマーインスタンス
    var timeCheck:Timer = new Timer(5000);
    // 5秒経過毎に実行されるイベントを指定
    timeCheck.addEventListener(TimerEvent.TIMER, onTick);
    // タイマースタート(イベント発生待ち)
    timeCheck.start();

   }

   // 5秒経過イベント
   public function onTick(event:TimerEvent):void {

    trace((event.target.currentCount * 5) + "秒経過");

   }

  ]]>
 </mx:Script>

</mx:WindowedApplication>

これの処理結果は、

5秒経過
10秒経過
15秒経過
20秒経過
...

のような表示が延々コンソールに出続ける。ちゃんと 5秒毎に TimerEvent.TIMER イベントが発生してるね!

「一定時間毎に、グローバル変数の値をチェック」したいのなら、onTick function のところに処理を記述する。

上の例だと無限にイベントが発生しているが、発生回数を制限したいのなら、タイマーインスタンスの生成時に、

var timeCheck:Timer = new Timer(5000, 5); // 5回まで

という具合に回数を指定してやれば良い。

簡単、簡単。

愛すべき正規表現の話。

XML オブジェクトに HTML ソースを突っ込んじゃうとエラーになるので、ソースの先頭に<?xml ~ ?> というタグが在るかどうかをチェックして、あれば XML ソースと判断しようかと。

Perl で書けば(取得したソースが、変数 $patternText に格納されているとすれば)、

if ($patternText =~ /<\?xml.*?\?>/s) {
 <処理>
}

という正規表現でバッチリだ。(/^<\?xml\s.*?\s\?>/s とした方が良いかもね~。ま、今回はテストなので、あまり厳密にならないように:-P)

これをそのまま、RegExp のオブジェクトとして、

var xmlPattern:RegExp = new RegExp("<\?xml.*?\?>", "s");
var matchText:String = xmlPattern.exec(patternText);

と書いてみたんだけど、全然マッチしねえの。(^^;
でも、

var xmlPattern:RegExp = /<\?xml.*?\?>/s;

って書くとマッチするなあ。つーか、この方が書きやすいし。
matchText にもちゃんとマッチした文字列として <?xml version="1.0" encoding="utf-8" ?> がセットされてるし、ばっちしじゃん。

ああ、ActionScript 3.0 コンポーネントリファレンスガイドに記述があった。
「ストリングリテラルの中では、単一の円記号として認識されるためには二重に円記号を入力する必要がある」だって。

確かに、

var xmlPattern:RegExp = new RegExp("<\\?xml.*?\\?>", "s");

と書いたら、ばっちりヒットしたわ。
ま、でも、//s な書き方の方がしっくりくるので、こういう書き方はもう二度としないだろうなあ。