Wicked Code
Silverlight のページめくりを簡単に
Jeff Prosise
コードのダウンロード : :
WickedCode2008_05.exe
(1110 KB)
Browse the Code Online

コンテンツ
2 年前、レドモンドにある Microsoft の廊下を歩いていると、友人に「ちょっと見せたいものがあるんだ」と呼び止められました。彼はノート PC をさっと開いて、私の人生を変えてしまうようなデモを見せてくれました。そのデモは、Silverlight
TM のページめくりのサンプルの初期バージョンでした。このサンプルは、
silverlight.net/samples/1.0/Page-Turn/default.html で見ることができます。
私は、自分が見ているものが信じられませんでした。そのデモは、なんとブラウザで動いていたのです。さらに驚くことに、Microsoft® .NET Framework も、Internet Explorer® も必要なく、その時点ではだれもわかりませんでしたが、後には Windows® すら必要なくなったのです。
PageTurn は、とても基本的な Silverlight 1.0 のデモです。私は、初めての人に Silverlight がどんなものかを印象付けたいときには、いつもこれを使っています。PageTurn の内部を見てみると、自分のページめくりアプリケーションを構築するのはそうたやすくないことがわかります。PageTurn は変形、クリッピング領域、動的に作成された XAML オブジェクトなどを使用しており、ソース コードを理解するには時間と労力 (と Silverlight に関する大量の知識) が必要になります。このデモは、Silverlight の最も高度な機能のいくつかを巧みに表現していますが、必ずしも汎用的な再利用のために設計されているわけではありません。
こうした理由から、Silverlight 1.0 アプリケーションにページめくり機能を驚くほど簡単に取り込むことが可能な、汎用のページめくりフレームワークを構築しました。私のフレームワークを使用すれば、アプリケーション全体を数行の JavaScript で構築することができます。Silverlight 自体の知識は最低限で済み、フレームワーク全体が約 500 行の JavaScript からなるため、中を見てその動作を理解することができます。数千行ものコードのあちこちを何度も見る必要はありません。もちろん、気が済むまで変更してかまいません。
PageTurnDemo アプリケーション
フレームワークを紹介する前に、その上に構築されたアプリケーションを見てみましょう。図 1 に示す PageTurnDemo アプリケーションの画面では、Microsoft Systems Journal (現在の MSDN® Magazine) の 1998 年 11 月号の最初の数ページをめくることができます (雑誌からページを転載するときに著作権の問題を避けるにはどうすればよいと思いますか。それはずばり、自分がかかわった雑誌のページの、自分が書いた記事を使用することです)。各ページはスキャンされたイメージですが、そのうちの最終ページは、イメージに Extensible Application Markup Language (XAML) テキストを重ねてあります。テキストを含めたのは重要な点を強調するためです。フレームワークで使用するページは、イメージだけに限定されません。一部の制約はあるものの、Image、TextBlock、MediaElement など、任意の XAML を含めることができます。
図 1 PageTurnDemo による部分的にめくられたページ (画像を拡大するには、ここをクリックします)
デモを実行するにはソース コードをダウンロードして Visual Studio
® 2008 から起動するか、
wintellect.com/silverlight/pageturndemo で見ることができます。雑誌の表紙が表示されたら (進行状況バーにより、アプリケーションで使用されているすべてのイメージを取得するためのダウンロードの進行状況が表示されます)、マウスの左ボタンを使用し、表紙の上でマウスを右から左にドラッグしてページをめくります。右側のページ上で左にドラッグし続けるとさらにページが表示され、左側のページ上で右にドラッグすると逆方向にページがめくられます。
このサンプルを作成するのがどのぐらい難しかったと思いますか。イメージのスキャン、トリミング、サイズ変更、ZIP ファイルへのパッケージ化以外は、何も難しいことはありませんでした。3 つの主要なソース コード ファイル (この記事に付属しているダウンロードの中にあります) は、HTML ファイル、関連する JavaScript ファイル、XAML ファイルです。イメージをダウンロードするための Silverlight ダウンローダ オブジェクトを除けば、ごくわずかしかソース コードがありません。実際、ページめくり機能に関係するのは 10 行程度しかありません。
ページめくりフレームワークの使い方
PageTurnDemo は、ページめくりフレームワークを使用するために実行することが必要な 4 つの基本的な手順を示しています。最初の手順は、PageTurn.js を HTML ファイルにインクルードすることです。これは、フレームワークの実装が格納されたスクリプト ファイルです。Default.html の対応する行を以下に示します。
<script ... src="PageTurn.js"></script>
第 2 の手順は、フレームワークをインスタンス化することです。ページめくりフレームワークは PageTurnFramework という名前の JavaScript クラスにカプセル化されているため、Default.html.js 内の以下のステートメントによってフレームワークがインスタンス化されます。
_ptf = new PageTurnFramework(_control,
_control.content.findName('PageTurnCanvas'));
PageTurnFramework コンストラクタに渡す第 1 パラメータは、Silverlight コントロールの参照です。第 2 パラメータは、ページが含まれているキャンバスの参照です。PageTurnDemo の XAML ドキュメントでは、キャンバス名は PageTurnCanvas です。
第 3 の手順は、ページをフレームワークに登録することです。各ページはキャンバスによって表され、ページを登録するために呼び出すことができる addPage メソッドが PageTurnFramework によって公開されています。AddPage は、以下のように 2 つのパラメータを受け取ります。
_ptf.addPage(_control.content.findName('EvenPage0'),
_control.content.findName('OddPage0'));
第 1 パラメータは、見開きで表示されるページの左側のページを表すキャンバスの参照です。第 2 パラメータは、右側のページを表すキャンバスの参照です。addPage を必要なだけ呼び出して、すべてのページ ペアを登録する必要があります。そして、最後の addPage の呼び出しの後に initializeFramework を呼び出します。
_ptf.initializeFramework();
initializeFramework メソッドは、フレームワークの内部状態を初期化します。その中で、ページのクリッピングと回転に使用されるいくつかの XAML オブジェクトが作成され、主要なイベントのハンドラが登録されます。
フレームワークを使用するための、最後となる第 4 の要件は、必ず適切にクリーンアップすることです。メモリ リークを避けるため、イベント ハンドラをプログラム的に登録するブラウザベースのアプリケーションは、ハンドラの登録を解除する必要があります。PageTurnFramework には、addPage と initializeFramework によって登録されたイベント ハンドラの登録を解除するメソッド dispose が含まれています。PageTurnDemo では、<body> 要素の onunload 属性により、ページがアンロードされたときにローカルな dispose 関数が呼び出されます。
<body ... onunload="dispose()">
このローカルな dispose 関数は Default.html.js の中にあり、フレームワークの dispose メソッドを呼び出します。
if (_ptf != null)
_ptf.dispose();
DOM イベントを使用して dispose を呼び出しているのは、Silverlight からはアンロード イベントが通知されないためです。window.unload イベントの方が好みであれば、同じく簡単に使用することができます。
PageTurnFramework クラスは、アプリケーションにページめくり機能を追加するために呼び出すことができる、6 つのパブリック メソッドを公開しています (図 2) を参照。PageTurnDemo は、そのうち addPage、initializeFramework、dispose の 3 つを使用しています。その他のメソッドは、他の機能を追加するために使用することができます。たとえば、Silverlight の PageTurn デモのように、アプリケーション内にページのサムネイルから成るナビゲーション バーを表示する場合、サムネイルがクリックされたときに対応するページに直接移動するには、PageTurnFramework.goToPage を呼び出します。

Figure 2 ページめくりフレームワークの API
| メソッド |
説明 |
| addPage |
ページ ペアをフレームワークに登録します。 |
| Dispose |
フレームワークによって保持されていたリソースを解放し、適切にクリーンアップします。 |
| getCurrentPageIndex |
現在表示されているページ ペアの 0 ベースのインデックスを返します。 |
| getPageCount |
addPage で追加されたページ ペアの数を返します。 |
| goToPage |
指定したページ ペアを表示します。 |
| initializeFramework |
ページめくりフレームワークを初期化します。 |
XAML 構造
ページめくりフレームワークでは、XAML ドキュメントの構造に対して、以下に示すいくつかの基本的な要件があります。
- ページ ペアの各ページは、XAML キャンバス (ページ キャンバス) で表現します。
- すべてのページ キャンバスのコンテナとなるページめくりキャンバスを追加します。
- ルート キャンバスに幅、高さ、背景色 (透明な場合でも必要です) を割り当てます。
第 3 の要件の理由は、初期化時にフレームワークがルート キャンバスから通知される MouseLeave イベントに対するハンドラを登録するためです。これらのイベントは、ページが部分的にめくられた状態でカーソルが Silverlight コントロールを離れた場合に、ページめくりを完了するために使用されます。
ページめくりフレームワークがページめくりの開始時に行うのと同様に、マウスをキャプチャするすべての Silverlight 1.0 アプリケーションは、ルート キャンバスからの MouseLeave イベントを処理して、その中でマウスを解放することをお勧めします。要素が MouseEnter や MouseLeave などのマウス イベントを受信するためには、その要素が Silverlight 内で "hit testable" である (マウス イベントを受け取ることが可能な) 必要があります。そのためには、要素 (この場合は Canvas) にサイズを設定し、背景が Null にならないようにします。
これらの要件に注意しながら図 3 を参照してください。この図は、ページめくりフレームワークで使用される XAML ドキュメントの一般的な構造を示します。ページめくりキャンバスは PageTurnFramework のクラス コンストラクタに渡すキャンバスです。ページ キャンバスは PageTurnFramework.addPage に渡されます。ページめくりキャンバスとページ キャンバスは、一般に明示的な幅と高さでタグ付けする必要があります。これは、キャンバスに含まれる XAML コンテンツの種類にかかわらず、フレームワークによって使用されるマウス イベントが適切に通知されるようにするためです。残りの部分は普通の XAML です。

Figure 3 XAML 構造
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="color" Width="width" Height="height">
<!-- Other XAML content goes here -->
<!-- Page turn canvas -->
<Canvas>
<!-- Canvases representing first page pair -->
<Canvas>
<!-- Content for left-hand page goes here -->
</Canvas>
<Canvas>
<!-- Content for right-hand page goes here -->
</Canvas>
<!-- Canvases representing additional page pairs go here -->
</Canvas>
<!-- Other XAML content goes here -->
</Canvas>
ページめくりキャンバスは、XAML ドキュメントの他のリッチ コンテンツと問題なく共存することができます。各ページは、単純な XAML イメージで構成することも、複雑な XAML レンダリングで構成することもできます。一般にページ キャンバスの Clip プロパティを使用するのは避けることをお勧めします。これは、フレームワーク自身がこのプロパティを使用するためです。しかし、ページ キャンバスの内部にサブキャンバスを宣言し、サブキャンバスの Clip プロパティを使用してクリッピング領域を定義することができます。
最初の左側のページを空白にして、最初の右側のページが開いていない本の表紙に見えるようにしたい場合は、最初の左側のページで不透明な四角形を宣言します。同様に、最後の右側のページに対して不透明な四角形を使用し、ユーザーが最後のページをめくったときに閉じた本のような外観にすることができます。PageTurnDemo は、雑誌のページをめくっているような錯覚を起こさせるために、両方を使用しています。
私が PageTurnDemo で行ったのと同じように、ページめくりフレームワークで使用するページを本や雑誌からスキャンするときは、内側の端に影ができることが多く、ページの外観がより実物に近くなります。スキャンしたイメージを使用しない場合は、数行の XAML で影をシミュレートすることができます。
図 4 に示すアプリケーションでは (私のラジコン飛行機とジェット機の一部を示すもので、
wintellect.com/silverlight/mymodels で参照することができます)、
図 5 に示す XAML の四角形の影を使用して、各ページ ペアの中心にある垂直な境界の周囲に影を作成しています。GradientStop の色のアルファ値により、左側のページでは右から左に四角形が薄くなり、右側のページでは左から右に薄くなっています。

Figure 5 ページの内側に影を作成するための XAML
<!-- Shadow on left-hand page -->
<Rectangle Canvas.Left="380" Canvas.Top="0" Width="20" Height="600">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="#00000000" Offset="0.0" />
<GradientStop Color="#40000000" Offset="1.0" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!-- Shadow on right-hand page -->
<Rectangle Canvas.Left="0" Canvas.Top="0" Width="20" Height="600">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="#40000000" Offset="0.0" />
<GradientStop Color="#00000000" Offset="1.0" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
図 4 ページの内側に XAML の影があるページめくりのサンプル (画像を拡大するには、ここをクリックします)
この説明でわかりにくい点がある場合は、PageTurnDemo を基にしてアプリケーションの構築を開始することができます。各ページの XAML を、各自の XAML で置き換えます。私のページ キャンバスの寸法は、幅が 400 で高さが 544 ですが、好きな寸法に変更してかまいません。
フレームワークの内部構造
ページめくりフレームワークは、この号のダウンロードに含まれている PageTurn.js の中で実装されています。PageTurnFramework クラスは、上から数行目から始まっています。JavaScript ではクラスがサポートされていませんが、このフレームワークは、ASP.NET AJAX や多くの Silverlight 1.0 アプリケーションで使用されているのと同じプロトタイプ パターンを使用して、JavaScript でクラスをシミュレートしています。錯覚に過ぎませんが、効果的です。
クラス コンストラクタは、内部的な状態を保存するために必要なすべてのインスタンス変数 (C# におけるフィールドに相当) を定義しています。たとえば以下のステートメントがあります。
これは、_control という名前の「フィールド」を定義し、コンストラクタに渡された Silverlight コントロールの参照で初期化しています。合計約 40 個のフィールドが宣言されており、進行中のページめくりのパーセント完了値 (_percent) から、登録済みのイベント ハンドラを表すトークンに至るまで (ハンドラの登録を解除するために dispose が使用します)、あらゆるものを保持するために使用されます。フィールドには、initializeFramework 内で動的に作成された XAML オブジェクトや、addPage の呼び出しで登録された XAML オブジェクトの参照も保存されます。
PageTurnFramework.prototype には、PageTurnFramework のすべてのメソッドが含まれています。メソッドは、大まかに 3 つのカテゴリのいずれかに分類されます。その分類とは、addPage や initializeFramework などのパブリック メソッド、マウス イベントやフレームワークが内部的に使用する Storyboard.Completed イベントに応答するイベント ハンドラ、フレームワーク内部で使用され、外部から呼び出されることは意図していないプライベート メソッドです。興味深いコードは多数ありますが、スペース上の制約があるため、PageTurn.js をダウンロードして中を確認してください。
フレームワーク アーキテクチャの興味深い側面の 1 つが、ページめくりの最中にマウス ボタンが離された (またはカーソルがコントロールから離れた) 場合にページめくりを完了する方法です。初期化時に、フレームワークは createFromXaml を使用して、タイマとして使用するための Storyboard オブジェクトを作成します。その後、Storyboard をページめくりキャンバスに追加し (これが、クラス コンストラクタにページめくりキャンバスの参照を渡す必要がある 1 つの理由です)、Storyboard.Completed イベントのハンドラを登録します。
不完全なページめくりを終了するため、フレームワークは Storyboard.begin を呼び出してタイマを開始します。タイマの刻みごとに、ページを一定の増分だけ前に進め (_step フィールドに格納されているステップ サイズを使用します)、ページめくりがまだ完了していない場合は、Storyboard.begin を再度呼び出します。ページを途中までめくってマウス ボタンを離すことで、このようすを見ることができます。ページを離したときにどこまでページがめくられていたかに応じて、完全に閉じた状態に戻るか完全に開いた状態になります。
Storyboard の XAML 定義は、initializeFramework メソッドの中で変数 _sb に格納されます。値が GUID の x:Name 属性が定義に含まれているのを不思議に思うかもしれません。Silverlight 1.0 では、createFromXaml で作成された Storyboards に名前を付けないと、createFromXaml が失敗します。Storyboard に名前を付ける必要がありましたが、アプリケーションで使用されている他の Storyboards と名前が衝突しないようにしたかったのです。そのため、GUID を使用して名前を付けました。
このアーキテクチャのもう 1 つの興味深い点は、フレームワークがマウス イベントを使用する方法です。ページを表すキャンバスに対し、addPage は MouseLeftButtonDown、MouseMove、MouseLeftButtonUp の各イベントに対するハンドラを登録します。左めくりは右側のページがクリックされたときに始まり、左マウス ボタンを押したままマウスが左に移動するに従って進行します。同様に、右めくりは左側のキャンバスがクリックされたときに始まり、マウスが右に移動するに従って進行します。イベント ハンドラで使用される重要なメソッドは _turnTo です。これは、部分的にめくられたページを、percent-complete パラメータが示す位置に進めます。
もっと詳しく調べたくなるフレームワークの最後の側面は、変形とクリッピング領域を使用してページめくりを表現する方法でしょう。initializeFramework は、2 つの PathGeometry オブジェクトを作成します。1 つは右側のページ用のクリッピング領域で、もう 1 つは左側のページ用のクリッピング領域です。また、RotateTransform と TranslateTransform が含まれる TransformGroup オブジェクトを作成します。
図 6 は、クリッピング領域と変形を使用して部分的にめくられたページを表現する方法を示しています。赤い三角形は、右側の最も上にあるページに対して使用するクリッピング領域です。マウスが左に移動するのに従って、クリッピング領域が小さくなり、上部のページの目に見える部分が減り、下にあるページの目に見える部分が増えます。青い三角形は、ページめくりが進むにつれて見えるようになる、左側のページに対して使用するクリッピング領域を表します。黄色の四角形は、ページのうち切り取られる部分を表します。
図 6 ページめくりを表現するために使用されるクリッピング領域と変形 (画像を拡大するには、ここをクリックします)
RotateTransform と TranslateTransform は、ページの位置と向きを設定するために組み合わされます (おわかりのように、多数の三角法が関係しています)。マウスが左に移動するに従い、黄色い四角形が左にスライドし、上の位置に回転するようすを想像してください。それと同時に、赤い三角形が徐々に小さくなるに従って、青い三角形のサイズが大きくなることを思い描くことで、ページめくりのしくみが理解できると思います。
結論
このコラムを書いてすぐ、私は PageTurnFramework API に対する他の便利な追加機能を検討してみました。たとえば、ページがめくられるたびにフレームワークによってイベントが通知されれば、ページ上の他のコンテンツを更新することができて便利です。また、たとえばめくっているページに付随する影の幅や、不完全なページめくりをアニメーション表示するために使用するステップ サイズなど、主なページめくりパラメータを制御するためのプロパティを公開すると便利かもしれません。
このフレームワークは、各自のプロジェクトで自由に使用および変更してかまいません。フィードバックは私にお送りください。機能セットや API に対する便利な追加を思いついたら、それについてもお知らせください。提案いただいた内容を盛り込み、ページめくりフレームワークのバージョン 2.0 をバージョン 1.0 より良いものにしたいと思います。
Jeff に対するご意見やご質問は、こちらまで英語でお送りください。 wicked@microsoft.com.
Jeff Prosise は、MSDN Magazine に寄稿している編集者で、『プログラミング Microsoft .NET』(日経 BP ソフトプレス、2002 年) など数冊の著書があります。また、ソフトウェアのコンサルティングおよび .NET Framework 専門の教育機関である Wintellect () の共同創立者でもあります。ご意見やご質問は、Jeff まで英語でお送りください。Jeff の連絡先は
wicked@microsoft.com です。