Curlでリッチプログラミング このページをアンテナに追加 RSSフィード

2008-06-08

[]起動中ダイアログを表示する(2) 00:56

時間のかかるアプレットの起動中にダイアログを表示する方法を前回紹介しました。前回は固定の(静的な)メッセージを表示するサンプルでしたが、今回は動くプログレスバーを表示する方法の紹介です。

アプレット起動中にプログレスバー動かすためにはサブアプレットという仕組みを使います。まずは次のようなクラスを定義します。

{import * from CURL.ENGINE.BROWSER}

{define-class public final SplashScreenManager
  
  field private applet-data:#AppletData
  field private applet-url:Url
  
  {constructor public {default}
    set self.applet-url =
        {string-url
            {stringify
                {curl 6.0 applet}
                {{Dialog
                     cursor = cursor-wait,
                     {ProgressBar indeterminate? = true},
                     {on e:WindowClose do {e.consume}}
                 }.show
                    title = "起動中",
                    modal? = false
                }
            }
        }
    {on-applet-suspend do
        {self.close}
    }
  }
  
  {method public {close}:void
    {if-non-null self.applet-data then
        {self.applet-data.destroy}
    }
  }
  
  {method public {show}:void
    def ag = {AppletGraphic}
    set self.applet-data = {AppletData ag, self.applet-url}
    {while self.applet-data.loading? do
        {dispatch-events true}
    }
    {if self.applet-data.what != "finished" then
        {self.close}
    }
  }
  
}

この SplashScreenManager クラスのポイントは

プログレスバーをモーダルダイアログで表示するだけのサブアプレットを作成します。

このサンプルでは別ファイルを作るのを少し横着してコンストラクタ内でサブアプレットをハードコードしています(stringifyマクロ内がそれ)。これは当然別の .curl ファイルにしても構いません。その場合は {string-url {stringify ...}} の部分を単純に {url "subapplet.curl"} などとします。

show メソッド内では AppletData というクラスを使ってサブアプレットインスタンス化しています(その次の while 文はサブアプレットのロード完了を待つためのコードです)。よって、この show メソッドが呼び出されるとサブアプレットが実行され、動くプログレスバー入りのダイアログが表示されます。

close メソッドでは、show メソッド内で生成された AppletData を destroy します。サブアプレットは終了してダイアログが閉じます。


以下はこの SplashScreenManager クラスを利用して起動中にプログレスバーを表示するサンプルです。(上記の SplashScreenManager クラスは splash-screen.scurl というファイルに定義されているものとします)

{curl 6.0 applet}
{curl-file-attributes character-encoding = "shift-jis"}
{applet manifest = "manifest.mcurl",
    {compiler-directives careful? = true}
}

{include "splash-screen.scurl"}

{def splash-screen = {SplashScreenManager}}
{splash-screen.show}

{import * from A-HEAVY-PACKAGE-TO-LOAD}

{View
    {Frame width = 8cm, height = 8cm, "サンプルアプレット"},
    visibility = "normal",
    owner = null,
    {on WindowClose do
        {exit}
    }
}

{splash-screen.close}

culrculr 2008/06/10 20:45 This is the similar idea what the splash screen feature is considered by Curl team long time ago :)

giuseppegiuseppe 2008/06/11 09:56 Hi Culr,
Indeed, this SplashScreenManager stuff is mainly based on Curl WBT source code I’ve looked at.

2008-06-01

[]起動中ダイアログを表示する 23:21

アプレットの起動に時間がかかる場合は、起動中のダイアログを表示したい場合があります。

以下はデタッチドアプレットで起動中ダイアログを表示するサンプルです。

最初にダイアログを表示して、Viewが表示された後にダイアログを閉じています。

{curl 6.0 applet}
{curl-file-attributes character-encoding = "shift-jis"}
{applet manifest = "manifest.mcurl",
    {compiler-directives careful? = true}
}

{def dialog = {Dialog
                  margin = 10pt,
                  "アプリケーションを起動しています。"
              }
}

{dialog.show title = "起動中", modal? = false}
{def saved-cursor = {{get-gui-manager}.set-busy-cursor}}
{dispatch-events true}

{import * from A-HEAVY-PACKAGE-TO-LOAD}

{View
    {Frame width = 8cm, height = 8cm, "サンプルアプレット"},
    visibility = "normal",
    owner = null,
    {on WindowClose do
        {exit}
    }
}

{{get-gui-manager}.restore-cursors saved-cursor}
{dialog.close "launched"}

このサンプルのポイントは以下の通り。

アプレットの起動時間の多くはパッケージのロードとコンパイルによります*1。特に初回起動時はパッケージキャッシュが存在しないため、2回目以降よりも時間がかかります。パッケージのロード&コンパイル中にもダイアログを表示するために、import文よりも前にダイアログ表示のコードを記述します。


  • ビジーカーソル(砂時計マウスカーソル)の設定には GuiManager.set-busy-cursor を使用する

必須ではありませんが、起動中ダイアログにビジーカーソルを設定できます。import 文は必ずトップレベルに記述する必要があるため with-busy-cursor マクロは使用できません。ここでは代わりに GuiManager.set-busy-cursor メソッドを直接呼び出しています。なお、これによって設定したビジーカーソルは GuiManager.restore-cursors によって復帰させます。


  • import 文の直前で dispatch-events を呼び出す

import の直前で dispatch-events を呼び出しています。これによってパッケージのインポートが始まる前にダイアログの描画処理を完了させます。


  • View に owner = null を指定する

些細なことですがこのケースでは View に owner = null を指定する必要があります。指定しなかった場合は View が起動中ダイアログの子ウィンドウとなってしまい、ダイアログを閉じたと同時に View も閉じてしまいます。

*1:当然アプリケーションの作りにもよります。

2007-04-21

[]forで回せる自作クラス 01:14

id:giuseppe:20070408では、for文のコンテナループの使い方を紹介しました。

あるルールに従うことで、自分で定義したクラスについてもfor文で回せるようになります。


例えば、FooListクラスのオブジェクトが、内部にFooItemクラスのオブジェクトを複数保持しているとします。

{define-class FooItem}

{define-class FooList
  field items:{Array-of FooItem} = {new {Array-of FooItem}}
}

このようなとき、FooListクラスに "get" という名前のメソッドと "for-loop-count" という名前のプロパティを適切に定義してやることで、for文で回せるクラスが出来上がります。

{define-class FooList
  {method {get i:int}:FooItem
    {return self.items[i]}
  }
  {getter {for-loop-count}:int
    {return self.items.size}
  }

getメソッドはひとつのint型引数を受け取り、FooItemを返します。一方、for-loop-countゲッターでは取得できるFooItemの数を返すようにします。

以上でFooListはfor文のコンテナオブジェクトとして扱うことができます。

let foo:FooList = {FooList}
||...
{for item:FooItem key i:int in foo do
    {do-something-with item}
}

forで回せるクラスを定義するためには、getメソッド/for-loop-countプロパティ以外にも、以下の方法があります。

  • to-Iteratorメソッドを実装する
  • getメソッド/keys-to-Iteratorメソッドを実装する

詳しくはAPIリファレンスの「for(マクロ)」の項を参照してください。

2007-04-08

[]コンテナループ 01:01

配列(Array-of)の要素を走査する場合には、

let arr:{Array-of int} = {{Array-of int} 1, 1, 2, 3, 5, 8, 13}
let sum:int = 0
{for i:int = 0 below arr.size do
    set sum = sum + arr[i]
}

というように書いてしまうかもしれませんが、curlではコンテナループという記法を使って

{for i:int = 0 below arr.size do
    set sum = sum + arr[i]
}

というfor文を、

{for n in arr do
    set sum = sum + n
}

と書くことができます。こちらの方が記述がシンプルなだけでなく、要素を繰り返し処理する意図が明確になります。この記法は、Array-ofに限らず、その他のコレクション型(HashTable-ofやSet-of)や文字列(String)、残余引数(...)に対しても使えます。知らなかった方はぜひ覚えておきましょう。

tatetate 2007/04/09 13:09 Curlのヘルプには「配列や文字列のような、コンテナの要素で繰り返し処理を行うのに for 式を使用する場合は、必ずコンテナ ループを使用してください。同じ状況で範囲ループの使用やインデックスのインクリメントを行なうと、最適なパフォーマンスが得られなくなる可能性があります。」とあります。パフォーマンスにどの程度の影響があるのかは分かりませんが、コンテナループを覚えておく第一義はパフォーマンス向上のためかなぁと思います。

giuseppegiuseppe 2007/04/09 18:50 ヘルプにそんな記述があったんですね。知りませんでした。。ただ、for文というのはマクロで、マクロというのは所詮プリプロセス時のコード変換なので、パフォーマンスに影響があるとは思いもしなかったんですが、一体どういう意味なんでしょう…
しかしいずれにせよ、コンテナループは、ループ変数や配列のサイズを意識しなくてよくなるというところに大きな意義があるのは間違いないです。つまらないバグが混入する可能性もなくなります。今回のfor文のような記法は一般的にもsyntax sugarなどと言われて、基本的に分かりやすさのために導入されるものです。Javaの拡張for文なども当然パフォーマンス目的ではなくて、EoD(Ease of Development)の思想のもとに導入されました。ただ、こういった考え方をするのは保守が命題のシステム開発の世界だけなのかな。組み込みでパフォーマンスにシビアな世界だったらそもそもOOPL使わずにC言語で作ったりするだろうし。

2007-03-21

[][]キャッシュと同期 00:41

curlのホームページキャッシュと同期に関するドキュメントがアップされました。

上記リンク先の中ほどに、「パッケージキャッシュのメリットを引き出す方法」というタイトルで当該文書へのリンクがあります。具体的には、curlキャッシュメカニズムを正しく効率的に使うための、各種推奨設定についての説明が記載されています。

上記ドキュメントは推奨設定についてのみ書かれているため、curlキャッシュメカニズム自体について理解したい方は開発者ガイドの以下を参照しましょう。

  • [Surge Lab 開発者ガイド > コンテンツのパッケージ > キャッシュと同期]

詳細は上述したドキュメントや開発者ガイドを参照してもらうとして、curlで再同期を制御するためにまず押さえておくべき事項は resync-as-of の指定方法です。基本は簡単で、例えば2007年3月21日にcurlアプリケーションを一部アップデートした場合は、アプレット宣言の中に以下のような resync-as-of 指定を含ませます。

{applet
    resync-as-of = {utc-date-time "2007-3-21"}
}

つまり、Webサーバにデプロイされているpcurlを新しいバージョンと差し替える場合などは、同時にその更新日時(pcurlを差し替えた日時)をアプレットファイル中の resync-as-of で指定するようにします。アプレットは起動時に resync-as-of 日時を読み取り、それよりも過去の日時にキャッシュされたコンテンツについて同期をとるように動作するため、差し替えられた新しいpcurlが正しく使われるという仕組みです。