MSDN マガジン > Home > 発行物 > 2008 > August >  書き込もう!: Silverlight 2 を使用して描画可能な Web アプリケーションを作...
書き込もう!
Silverlight 2 を使用して描画可能な Web アプリケーションを作成する
Julia Lerman

この記事では、次の内容について説明します。
  • Silverlight の InkPresenter コントロール
  • Web アプリケーションのインク
  • 手書き認識
  • 透明な背景とイメージを使用した背景の作成
この記事では、次のテクノロジを使用しています。
Silverlight 2、Expression Blend、Visual Studio 2008
この記事は、Visual Studio 2008 SP1、Silverlight 2、および Expression Blend のプレリリース版に基づいて書かれています。ここに記載されているすべての情報は、変更される場合があります。
Silverlight は、マイクロソフトがここ数年の間に生み出した、最も魅力的な新しい Web テクノロジの 1 つです。Silverlight の登場は非常に大きな出来事でした。毎年行われる MIX カンファレンスのテーマが、今や Silverlight™ とその豊富な機能に集中しているように思えるほどです。Silverlight 1.0 が最初にリリースされたのは 2007 年です。そして Silverlight 2 には、その最終リリースに先立って、新しいリリースごとに魅力あふれる新機能が追加されています。これまで相応の注目を浴びてこなかった Silverlight のすばらしい機能の 1 つが、InkPresenter コントロールです。InkPresenter コントロールを使用すると、インターネット ユーザーはブラウザから直接 Silverlight アプリケーションに描画できるようになります。
Silverlight が動作するさまざまな OS やブラウザで InkPresenter も使用できます。InkPresenter の機能によって、ブラウザ、OS、およびハードウェアに関係するいくつかの制限がさらに解消されることになります。ここで紹介するサンプル アプリケーションでは、Silverlight 2 の持つ Microsoft® .NET Framework のプログラミング機能と InkPresenter を組み合わせて使用することで、事前定義済みのイメージ コレクションへの注釈の追加、手書き認識の実行、サーバー側データベースへの注釈と認識されたテキストの保存、選択されたイメージの注釈の取得、および関連付けられているテキストに基づくイメージのフィルタ処理を、ユーザーが行えるようにしています。データベースおよび手書き認識機能は Windows® Communication Foundation (WCF) サービスにより提供されます。図 1 に、完成したアプリケーションを示します。
図 1 完成したアプリケーション (クリックすると拡大画像が表示されます)
この記事で説明するあらゆる作業は、Silverlight 1.0 でも行うことができます。事実、Silverlight 2 がまだ手元になかったときに、Silverlight 1.0 でまったく同じ機能を使用して同様のアプリケーションを作成した経験があります。ただし、.NET を対象とするコードをクライアント側で自由に使用できない場合は、より多くの機能を実装するために、.NET Web サービスを活用する必要があります。

InkPresenter の概要
このアプリケーションの作成に不可欠な InkPresenter コントロールは、ストロークのコレクションのコンテナです。各ストロークは、StylusPoints のコレクションから構成されます。Silverlight には Shape クラスのメンバである別種の Stroke プロパティが存在するため、これらを混同しないようにしてください。
ペンと紙があるとしましょう。あなたがペンを紙に当てるたびに、そこからストロークが始まります。次に、あなたはペンを動かして、円を描いたり、単語を書いたりします。ペンを紙から離した箇所はストロークの終端になり、再びペンを当てたところは新しいストロークの開始点となります。
ストロークは、色、幅、その他いくつかの属性など、その特徴を定義する DrawingAttributes を持ちます。ストロークの個々のポイントにも、位置を表す X 座標と Y 座標、および PressureFactor というプロパティがあります。PressureFactor は、デジタイザを備えたコンピュータに対して解釈されます。PressureFactor を使用することで、ユーザーがスタイラスをデジタイザに押し当てたときの圧力の量を基に、ストロークをプログラムで変化させることができます。図 2 にクラス階層を示します。
図 2 InkPresenter クラス (クリックすると拡大画像が表示されます)
Silverlight の他のビジュアル要素と同じように、InkPresenter オブジェクトとその子オブジェクトも XAML で表現できます。図 3 に、InkPresenter 内の 3 つの短いストロークを示します。図 4 には、StrokeCollection の 1 つのストロークの XAML 表現を示します。ストロークが短くても、デジタイザは非常に多くのデータを収集します。同じテストをマウスを使用して行った場合、収集されるデータははるかに少なくなります。スタイラス ポイントは倍増しますが、ポイントの収集間隔が短くなるためです。
図 3 3 つのストローク
<StrokeCollection>
<StrokeCollection xmlns="http://schemas.microsoft.com/client/2007">
  <Stroke>
    <Stroke.DrawingAttributes>
      <DrawingAttributes Color="#FF000000"         OutlineColor="#00000000" Width="3" Height="3" />
    </Stroke.DrawingAttributes>
    <Stroke.StylusPoints>
      <StylusPoint X="81.4583358764648" Y="96.5833282470703" />
      <StylusPoint X="81.4583358764648" Y="96.5833282470703" />
      <StylusPoint X="81.0833358764648" Y="96.4166717529297" />
      <StylusPoint X="81.0833358764648" Y="96.4166717529297" />
      <StylusPoint X="81.0833358764648" Y="96.4166717529297" />
      <StylusPoint X="81.0833358764648" Y="96.4166717529297" />
      <StylusPoint X="81.0833358764648" Y="96.4166717529297" />
      <StylusPoint X="80.4583358764648" Y="96.8333282470703" />
      <StylusPoint X="80.4583358764648" Y="96.8333282470703" />
      <StylusPoint X="80" Y="97.2916717529297" />
      <StylusPoint X="80" Y="97.2916717529297" />
      <StylusPoint X="79.625" Y="97.75" />
      <StylusPoint X="79.625" Y="97.75" />
      <StylusPoint X="79.625" Y="97.75" />
      <StylusPoint X="79.625" Y="97.75" />
      <StylusPoint X="79.625" Y="96.5416717529297" />
      <StylusPoint X="79.8333358764648" Y="95.7083358764648" />
      <StylusPoint X="80.25" Y="94.7916641235352" />
      <StylusPoint X="80.7916641235352" Y="93.5416641235352" />
      <StylusPoint X="81.5" Y="92.125" />
      <StylusPoint X="82.4166641235352" Y="90.4583358764648" />
      <StylusPoint X="83.4583358764648" Y="88.5833358764648" />
      <StylusPoint X="84.75" Y="86.5416641235352" />
      <StylusPoint X="86.1666641235352" Y="84.3333358764648" />
      <StylusPoint X="87.7083358764648" Y="82.1666641235352" />
      <StylusPoint X="89.25" Y="79.9166641235352" />
      <StylusPoint X="90.75" Y="77.9583358764648" />
      <StylusPoint X="92" Y="76.0833358764648" />
      <StylusPoint X="93.1666641235352" Y="74.8333358764648" />
      <StylusPoint X="94" Y="73.625" />
      <StylusPoint X="94.7083358764648" Y="73.1666641235352" />
      <StylusPoint X="95.125" Y="73.1666641235352" />
      <StylusPoint X="95.125" Y="73.1666641235352" />
      <StylusPoint X="95.125" Y="73.1666641235352" />
      <StylusPoint X="94.7083358764648" Y="73.5" />
    </Stroke.StylusPoints>

  </Stroke>
...
</StrokeCollection>
InkPresenter を使った作業は、結局のところ、そのストロークを作成してやり取りするプロセスです。ただし、既定では、InkPresenter でこれらを行うことはできません。InkPresenter にはイベントとメソッドが用意されているため、StrokeCollection を追加および削除したり、Strokes にアクセスしてやり取りしたりできます。ただし、InkPresenter コントロールの境界内でマウスやスタイラスの操作をプログラムで追跡したり、ストロークを作成したりするのは、開発者の仕事です。

Tablet PC が不要になる
Windows Presentation Foundation (WPF) と Silverlight が登場するまで、開発者は、Tablet PC の描画機能を活用するカスタム プログラムを作成するために Tablet PC SDK を使用する必要がありました。この SDK は、.NET、Visual Basic® 6.0、および C++ での開発を可能にする .NET ラッパーを備えた COM API のセットです。OS としては、Windows XP Tablet PC Edition が不可欠でした。
Tablet PC SDK 1.7 からは、重要なコントロールである InkOverlay を実行する場合に、システムに完全な信頼を付与する必要がなくなりました。ActiveX® コントロールと同じように、インク対応の Windows フォーム コントロールを作成し、それらを Web ページに埋め込むことができるようになりました。ActiveX コントロールは Internet Explorer® 上でしか使用できませんでしたが、開発者が描画機能やその他のインク機能を Web に導入するための足場となりました。
Windows Vista® では、Tablet PC の機能は一級市民として OS に組み込まれています。また、タブレット機能の開発用 API は、WPF InkCanvas オブジェクトに統合されました。したがって、.NET Framework 3.0 がインストールされているコンピュータであれば、Tablet PC でなくてもタブレット機能をサポートできます。ただし、タブレットのデジタイザ上でスタイラスを使用した場合、ユーザーがマウスを使用した場合よりも解像度が桁違いに高くなることは認識しておいてください。
Silverlight のインク機能は、WPF で使用できる機能のサブセットです。ただし、両者には大きな違いが 1 つあります。WPF には InkCanvas があり、インクを InkCanvas 上に表示するための InkPresenter と呼ばれるプロパティを設定できますが、Silverlight には InkCanvas がないため、InkPresenter を直接操作する必要があります。
最も重要なことは、Silverlight の使用は Windows や Internet Explorer 上だけに制限されないため、より多くの環境でインク対応の Web サイトを利用できるという点です。

InkPresenter の基本
Visual Studio® 2008 用 Silverlight Tools により Silverlight XAML デザイナが Visual Studio 2008 に埋め込まれますが、デザイン画面自体は読み取り専用であるため、InkPresenter の一部のデザイン作業は Expression Blend™ 2.5 で行った方が便利かもしれません。2 つのアプリケーションは適切に統合されているので、作業は非常に簡単です。また、Expression Blend に慣れると、大いに楽しみつつ作業できるようになります。
私は、プロジェクトを Visual Studio で開始する方法が気に入っています。既定のプロジェクト テンプレートが非常に便利だからです。そして、XAML で何らかのデザイン作業を行うときは、Expression Blend でプロジェクトを開きます。プロジェクトは Visual Studio IDE から簡単に開くことができます。ソリューション エクスプローラで XAML ファイルを右クリックし、ファイルを Expression Blend で開くオプションを選択してください。Expression Blend 2.5 を持っていない場合は、Visual Studio 2008 で XAML を手動で編集し、変更の結果をすぐに確認することができます。
Silverlight プロジェクトを作成する場合は、Visual Basic と C# のどちらを使用するかを選択できます。私が仮に Visual Basic のスキルに長けているとしても、このプロジェクトについては C# で作業します。Silverlight 1.0 での作業で、C# に簡単に移植できる JavaScript コードを蓄積してきたからです。
Visual Studio でプロジェクトを作成した後、デザインを開始できるように、まず Expression Blend でプロジェクトを開きます。Expression Blend デザイナで XAML ファイルを開いたら、InkPresenter コントロールをデザイン画面に追加できます。InkPresenter コントロールを探すには、ツールボックスの下部にある [>>] アイコンをクリックして、Expression Blend の [アセット ライブラリ] を開く必要があります。その後で [すべて表示] チェックボックスをオンにすると、InkPresenter など使用頻度のあまり高くない各種コントロールが表示されます。また、検索ボックスを使用してコントロールを検索することもできます。
InkPresenter を XAML デザイン画面にドラッグしてから、プロパティ ウィンドウでコントロールに名前を付けます。私は "inkP" という名前を選びました。この名前は、この記事で後ほど紹介するコードで頻繁に使用されています。
InkPresenter がデザイン画面上で選択されている状態では、その境界が表示されますが、InkPresenter が選択されていない状態では、境界は表示されません。InkPresenter コントロールはインクを描画できるコンテナです。InkPresenter には背景のプロパティはありますが、Fill、Stroke (境界線用) など、他のコントロールにあるその他多くのプロパティはありません。そのため、視覚的な境界線を追加するには、他のコントロールが必要になります。
シンプルな例として、位置とサイズが InkPresenter と同じ Rectangle をキャンバスに追加してみましょう。既定では、Rectangle は StrokeThickness が 1 の黒色の境界線 (Stroke プロパティ) を持ちます。私が "inkBorder" と名付けたその Rectangle と InkPresenter は、キャンバスにおいて兄弟の関係になります。InkPresenter の Z オーダーは、Rectangle より高くする (Rectangle の上にする) 必要があります。そうしないと、Rectangle の背後に InkPresenter が隠れている状態になります。
InkPresenter コントロールをデザイン画面上で見つけるのは難しいため、これを選択する必要がある場合は、[オブジェクトとタイムライン] パネルで指定するのが最も簡単です。この操作により、コントロールがデザイン画面上で強調表示されます。F5 キーを押してここまでのソリューションをテストすると、Rectangle の境界線が表示されます。ただし、マウスやスタイラスを使用してその内部に描画しようとしても、まだ何も起こりません。

イベントと背景を追加する
既に説明したように、InkPresenter はストローク コレクションのコンテナにすぎず、それ自体ではストロークを作成できません。ストロークを作成するには、プログラムで InkPresenter 上のイベントに応答する必要があります。ストロークをキャプチャする際の主要なイベントは、MouseLeftButtonDown、MouseMove、および MouseLeftButtonUp です。InkPresenter が MouseLeftButtonDown イベントを受け取った際に、メモリに新しいストロークを作成し、それを InkPresenter の StrokeCollection に追加する必要があります。マウスが InkPresenter の内部を移動して MouseMove イベントが発生した際には、StylusPoint をその Stroke に追加します。ユーザーがスタイラスをデジタイザから離すか、マウス ボタンを離すことにより、MouseLeftButtonUp イベントが発生した場合には、ストロークを終了させる必要があります。
Expression Blend 2.5 では、[プロパティ] ウィンドウを使用することで、イベントをコントロールに簡単に設定できます。inkP コントロールを選択し、[プロパティ] ウィンドウ上部のイベント アイコンをクリックしてコントロールのイベントを表示します。次に、前に述べた 3 つのイベント ハンドラそれぞれのテキスト ボックスをダブルクリックします。イベント ハンドラごとに、対応するメソッドが Visual Studio の XAML コントロールの分離コードに追加されます。Visual Studio と Expression Blend の統合は、双方向で機能します。変更を加えるたびに Visual Studio から確認を求められるので、タスク バー上で点滅している Visual Studio アイコンに注意してください。
3 つのハンドラを作成したら、Visual Studio に切り替えて図 5 のコードを追加します。このコードにより、ユーザーがコントロールを操作した際のストローク データを InkPresenter で収集および表示できるようになります。このコードは、前に示したタスク (新しいストロークの作成とスタイラス ポイントの追加) を実行します。スタイラス ポイントは、MouseLeftButtonDown イベントと MouseMove イベントの MouseEventArgs を通じてアクセスされます。
System.Windows.Ink.Stroke newstroke;

void inkP_MouseLeftButtonDown(object sender, MouseEventArgs e)
{
  inkP.CaptureMouse();
  newStroke = new System.Windows.Ink.Stroke();
  newStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(inkP));
  inkP.Strokes.Add(newStroke);
}
void inkP_MouseMove(object sender, MouseEventArgs e)
{
  if (newStroke != null)
  {
    newStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(inkP));
  }
}
void inkP_MouseLeftButtonUp (object sender, MouseEventArgs e)
{
  newStroke = null;
  inkP.ReleaseMoustCapture();
}
このパズルには、もう 1 つピースがあります。InkPresenter には、マウス イベントを受け取るための Background プロパティが必要です。背景は他のコントロールの Fill プロパティと似ていますが、Fill で実行できるような追加のカスタマイズはありません。デザイン シナリオに応じて背景を独創的なものにすることもできますが、ここでは、単純に Background プロパティを "Transparent" に設定します。
この背景を Expression Blend で設定するには、InkPresenter コントロールの [プロパティ] ウィンドウを使用し、背景に単色ブラシを選択して、そのアルファ値を 0 に設定します。また、単純に「Background="Transparent"」と XAML に直接入力することもできます。
次の XAML では、イベントを設定して Background プロパティを指定した後の 2 つのコントロールを示しています。
<Rectangle Margin="20,30,35,24" 
  x:Name="inkBorder" Stroke="#FF000000"/>
<InkPresenter Margin="20,30,35,24" 
  x:Name="inkP" 
  MouseLeftButtonDown=
    "inkP_MouseLeftButtonDown" 
  MouseLeftButtonUp=
    "inkP_ MouseLeftButtonUp" 
  MouseMove="inkP_MouseMove" 
  Background="Transparent" 
  Opacity="1"/>
これで、Expression Blend または Visual Studio からプロジェクトを実行すると、描画されたストロークが InkPresenter 上に表示されるようになりました (図 6 を参照)。
図 6 InkPresenter 上のストローク

よりスタイリッシュな InkPresenter
既に説明したように、InkPresenter はコンテナですが、Rectangle などの他のビジュアル要素よりもキャンバスに似ています。InkPresenter を視覚的におもしろいものにするためには、InkPresenter を他の要素と結び付ける必要があります。そうしないと、Silverlight を真に活用しているとは言えません。そこで、既存の XAML を Expression Blend で開き、前に InkPresenter の境界線を作成するために追加した Rectangle を使用して、デザインに磨きをかけましょう。
まず、境界線の Rectangle の角に丸みを持たせましょう。Rectangle を選択し、その RadiusX プロパティと RadiusY プロパティを 25 に変更します。これで、Rectangle の角は素敵な丸みを帯びました。ただし、InkPresenter の角は目に見える境界線からはみ出しており、インクの描画が可能な状態になっています。この問題を解決するには、InkPresenter の境界を変更して (つまりクリップして)、目に見える境界線に合わせます。そのためには、Silverlight のクリップ機能を使用して InkPresenter の形状を変更します。
Expression Blend では、要素を簡単にクリップして他の要素の形状に合わせることができます。ただし、これを行う前に、inkBorder の Rectangle のコピーを作成し、後で使用できるようにしておいてください。[オブジェクトとタイムライン] ウィンドウで、新しい Rectangle を右クリックします。そのコンテキスト メニューから [パス] を選択し、[クリッピング パスの作成] を選択します。パスによりどのオブジェクトをクリップするか (つまり、どのオブジェクトに Rectangle の形状を適用するか) を選択するよう求めるウィンドウがポップアップ表示されます。InkPresenter を選択してください。この操作の結果、2 つの処理が行われます。InkPresenter は Rectangle と同じ形状になり、新しい Rectangle は削除されます。Rectangle が InkPresenter のクリッピング パスになったため、Rectangle はオブジェクトではなくなりました。Rectangle をコピーしておいた理由がおわかりでしょう。
結果の XAML は図 7 のようになります。プロジェクトを実行して、InkPresenter の新しい枠を確認してください。図 8 のように表示されます。Silverlight では、あらゆる形状をクリッピング パスとして使用できます。そのため、InkPresenter を好きな形状にすることができます。図 9 に、InkPresenter のクリップに使用できるランダムな形状の例を示します。
<Rectangle x:Name="inkBorder" Width="346" Height="234"
  Stroke="#FF000000" Canvas.Top="25" Canvas.Left="25" 
  RadiusX="25" RadiusY="25"/>
<InkPresenter x:Name="inkP"
  Width="607" Height="408" Canvas.Left="25" Canvas.Top="34"
  MouseLeftButtonDown="inkP_MouseLeftButtonDown" 
  MouseLeftButtonUp="inkP_MouseLeftButtonUp" 
  MouseMove="inkP_MouseMove" 
  Background="Transparent"
  Clip="M0.5,25.5 C0.5,11.692881 11.692881,0.5 25.5,0.5 L581.5, 0.5 C595.30712,0.5 606.5,11.692881 606.5,25.5 L606.5, 382.5 C606.5, 396.30712 595.30712,407.5 581.5,407.5 L25.5,407.5 C11.692881, 407.5 0.5,396.30712 0.5,382.5 z" >
</InkPresenter>
図 8 InkPresenter の枠をクリップする
図 9 InkPresenter をクリップするために使用した、ランダムに描画された形状 (クリックすると拡大画像が表示されます)

あの Silverlight の外観を手に入れる
Silverlight では、透明度を使用して魅力的なビジュアル レイヤを作成できます。InkPresenter も、表面を半透明にすることができます。この効果を実現するには、まず背景のイメージをキャンバスに追加する必要があります。ここでは、Silverlight.net Web サイトの背景を使用しました (silverlight.net を参照)。イメージをキャンバスにドラッグし、その Stretch プロパティを Fill にすることで、簡単に背景を設定できます。そのイメージの Z オーダーが、デザイン画面の一番初めにリストされるコントロールになっていることを確認してください。一番初めにリストされていないと、イメージが Rectangle と InkPresenter の上に配置され、両方とも見えなくなります。
次に、Rectangle を変更して背景を黒色にします。その 1 つの方法が、[プロパティ] ウィンドウでフィル ブラシを選択し、その R、G、および B 値を 0 に設定することです。次に、Rectangle の [Opacity] を 10% に変更して半透明にします。
InkPresenter の背景色を設定し、それを半透明にすることはできますが、この透明度はインクにも同じように適用されます。私は、InkPresenter の背景を完全に透明のままにしておき、他のコントロールを使用して効果を追加する方が好きです。コントロールの背景色のアルファ値を変更してみて、それをコントロールの不透明度を変更した場合の効果と比較するとおもしろいかもしれません。DrawingAttribute の Color プロパティと OutlineColor プロパティの Alpha プロパティを使用して、インクの透明度を直接変更することもできます。これには、WPF InkCanvas と Tablet PC SDK に存在する DrawingAttribute.Transparency プロパティとまったく同じ効果があります。図 10 に、半透明の Rectangle と組み合わせることで描画の背景に魅力的なビジュアル効果を追加した、InkPresenter を示します。
図 10 半透明の背景を作成する

背景のイメージまたはビデオを InkPresenter に追加する
半透明の背景は一部のシナリオでは非常に魅力的なソリューションですが、イメージやビデオを背景として使用することもできます。このような背景を作成する場合、InkPresenter の実際の Background プロパティは変更しません。イメージやビデオは、InkPresenter オブジェクトの子要素として追加します。子要素に Height、Width、Left、Top のプロパティがない場合は、親の InkPresenter のプロパティを継承します。
Expression Blend を使用してイメージ要素を XAML デザイン画面に追加し、そのイメージを InkPresenter にドラッグしてみてください。または、次の XAML を直接追加することもできます。
<InkPresenter x:Name="inkP"
  Width="607" Height="426" Canvas.Left="25" Canvas.Top="34"
  MouseLeftButtonDown="inkP_MouseLeftButtonDown" 
  MouseLeftButtonUp="inkP_MouseLeftButtonUp" 
  MouseMove="inkP_MouseMove" 
  Background="Transparent">
  <Image Source="Assets/Leaves.jpg" Stretch="Fill" />
</InkPresenter>
これで、イメージ上に直接描画できるようになりました。また、イメージの Opacity の値を小さくすると、同じように半透明にすることができます (図 11 を参照)。
図 11 Opacity プロパティを使用して半透明にする (クリックすると拡大画像が表示されます)
ビデオの追加も、同様に簡単です。Image の代わりに MediaElement を使用します。Expression Blend によるビデオの処理方法は、イメージとは異なります。ビデオを Silverlight プロジェクトから XAML デザイン画面にドラッグ アンド ドロップすることはできますが、プロジェクトを実行したときにファイルが見当たらなくなります。そこで、ビデオをホスト Web プロジェクトの内部に配置する必要があります。そして、MediaElement のソース属性でファイルの URL を参照する必要があります。MediaElement の場合、Image のように InkPresenter が自動的に塗りつぶされることはありません。サイズを手動で調整するか、「Stretch="Fill"」を XAML に直接追加する必要があります。InkPresenter の背景で再生されるビデオの例を次に示します。
<InkPresenter x:Name="inkP" . . .   >
  <MediaElement Height="246" x:Name="Butterfly_wmv" Width="345"
    Source="http://localhost:52476/MSDNMagAnnotationClient_Web/Assets/Butterfly.wmv"
    Stretch="Fill"/>
</InkPresenter>
MediaElement の使用と操作の詳細については、Silverlight のドキュメントを参照してください。

ストロークのデザインの基礎
既定では、ストロークの既定の描画属性により、高さと幅が 3 の黒色のストロークが作成されます。この値はデバイスに依存しないピクセル (DIP: Device Independent Pixel) であり、2 未満の値に設定することはできません。
penWidth や penColor などの各種ストローク属性に影響を及ぼすメソッドおよびイベント ハンドラは、コードで作成できます。たとえば、currentColor という変数の値は、コントロールのクリック イベントにより変更できます。currentColor は、新しいストロークが作成される際の inkP_MouseLeftButtonDown イベントで使用できます。
これを試してみましょう。次のように変数宣言をクラスに追加し、既定値を黒色に設定してください。
System.Windows.Media.Color currentColor =        Colors.Black;
次の例では、任意の数の色付き Rectangle に対する MouseLeftButtonDown イベントとして使用できる単一のメソッドを作成しています。このメソッドは Rectangle の色を判断し、その色を currentColor 変数の値として使用します。
private void ChangeColor(object sender, MouseButtonEventArgs e)
{
  Rectangle rec = (Rectangle)sender;
  SolidColorBrush scb = (SolidColorBrush)rec.Fill;
  currColor = scb.Color;
}
inkP_MouseLeftButtonDown メソッドに、DrawingAttributes を currentColor 変数に設定するコードを追加します。
newStroke.DrawingAttributes.Color = currentColor;
最後に、変更をトリガする手段が必要になります。2 つの Rectangle 要素を追加し、一方の Fill を黒色に、もう一方の Fill を赤色に設定します。各 Rectangle には ChangeColor メソッドを呼び出すための MouseLeftButtonDown イベントが必要です。この XAML により、円として描画される 2 つの Rectangle オブジェクトが作成されます。
<Rectangle MouseLeftButtonDown="ChangeColor" 
  Width="24" Height="22" Fill="#FF000000" 
  Stroke="#FF000000" RadiusX="25" RadiusY="25"
  Canvas.Left="-10" Canvas.Top="282"/>
<Rectangle MouseLeftButtonDown="RedInk"
  Width="24" Height="22"
  Fill="#FFCE0C0C" Stroke="#FF000000" 
  RadiusX="25" RadiusY="25"
  Canvas.Left="25" Canvas.Top="282"/>
また、StrokeCollection に手を加えるコードを記述して、既存のストロークの色を変更することもできます。
Stroke オブジェクトの DefaultDrawingAttributes の 1 つが OutlineColor です。イメージなど、マルチカラーの背景上で描画することを計画している場合は、描画されるインクに対して同じ色の輪郭を設定するとよいでしょう。inkP_MouseLeftButtonDown イベントにコードを追加して、newStroke の OutlineColor を設定できます。
newStroke.DrawingAttributes.OutlineColor = 
  Colors.White;
図 12 に、これを実際に行ったストロークを示します。
図 12 境界線を持つインク (クリックすると拡大画像が表示されます)

手書き認識
アプリケーションにおけるすばらしいインク機能の 1 つが、手書き認識です。この機能は当初から Tablet PC SDK に含まれており、WPF の機能でもありますが、Silverlight には含まれていません。ただし、認識を行って結果を返す ASP.NET Web サービスまたは WCF サービスにストローク データを送信することで、Silverlight アプリケーションでも手書き認識を使用できます。
Microsoft Research は、100 万人を超える人々の手書きサンプルを使用して、手書き認識のためのアルゴリズムを作成しました。驚かれるかもしれませんが、ブロック体よりも筆記体の方がうまく認識できます。さまざまなアジア系言語を含め、多数の言語に対応した手書き認識エンジンもあります。
認識エンジンは、ストロークのコレクションを分析します。また、ストロークの種類 (単語、文、段落、図形など) を識別できます。認識エンジンは、複数の単語から成るストローク セット内にある個々の単語を特定し、その単語を 1 つの単位として認識できます。次に、そのサンプルとアルゴリズムを使用して、その単語が表している可能性のある候補のセットを返します。ユーザーが単語のセット (文など) を送信した場合、エンジンは単語のセットと一連の候補を返します。これまで、認識を実行できるのは Tablet PC に限定されていました。それが今や、Silverlight がサポートされているすべてのコンピュータとブラウザで実行できるようになったのです。

InkAnalyzer クラス
System.Windows.Ink.InkAnalyzer は手書き認識を行います。このクラスの使用方法は、驚くほど簡単です。 ストロークのコレクションを InkAnalyzer オブジェクトに渡し、その Analyze メソッドを呼び出し、Successful というブール型プロパティを使用して Analyze (分析) が成功したかどうかを特定するだけです。Successful が true の場合、GetRecognizedString メソッドは最も信憑性の高い推測文字列を返し、GetAlternates メソッドは候補の文字列の配列を返します。
InkAnalyzer クラスは Silverlight API には含まれていませんが、Web サービスまたは WCF サービスを使用することで、手書き認識機能を Silverlight アプリケーションに組み込むことができます。Web サーバーで WPF API をホストし、認識を行うための機能を提供できます。ただし、そのためには、何回か変換を行う必要があります。
まず、インク認識を行うコンポーネントで図 13 の API を参照する必要があります。最初の 2 つの API は、Visual Studio の [参照の追加] インターフェイスを通じて簡単に利用できます。残りの 2 つは、Tablet PC SDK と共にインストールされ、Program Files\Reference Assemblies\Microsoft\Tablet PC\v1.7 に格納されます。コーディングする際には、IACore 名前空間と IAWinFX 名前空間の間のあいまいな参照に注意してください。最後に、Tablet PC SDK と共にインストールされている IALoader.dll も参照する必要があります。Windows Vista を実行している場合、このファイルは C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin に格納されています。
API 機能
PresentationCore.dll System.Windows.Ink API が含まれています。
WindowsBase.dll コレクションにより使用される機能が含まれています。
IAWinFX.dll InkAnalyzer クラスと機能を System.Windows.Ink に追加します。
IACore.dll InkAnalyzerBase クラスと機能を System.Windows.Ink.AnalysisCore を通じて提供します。
ストロークを InkPresenter からネットワーク経由でサービスに転送するには、ストロークをシリアル化する必要があります。ところが、これらはシリアル化が不可能なオブジェクトです。そのため、StrokeCollection を文字列ベースで表した XAML 表現を作成する必要があります。その後で、この文字列をシリアル化し、サービスに送信することができます。
Silverlight 1.0 では、この文字列表現は JavaScript を使用して作成する必要がありました。Silverlight 2 には、LINQ to XML を使用できるという利点があります (Visual Basic を使用している場合は、さらに XML リテラルを使用することもできます)。
図 14 に示すコードは、DrawingAttributes、StylusPoints、およびそれらの詳細を読み取るため、StrokeCollection オブジェクトを詳細にカスタマイズしたものです。また、LINQ to XML を使用して、StrokeCollection の XAML 表現を作成しています。このコードは、手書き認識など、多数の目的で使用できます (ただし、DrawingAttributes は認識機能では無視されます)。認識だけを目的にこのメソッドを作成する場合は、DrawingAttributes を収集するコードを削除してもかまいません。
public XElement StrokestoXAML(StrokeCollection mystrokes)
{
  //this method uses LINQ to XML
  //be sure to add the namespace to each element in order to load back
  //into a new StrokeCollection later with the XAMLReader

  string xmlnsString = "http://schemas.microsoft.com/client/2007";

  XNamespace xmlns = xmlnsString;
  XElement XMLStrokes = new XElement(xmlns + "StrokeCollection",
      new XAttribute("xmlns", xmlnsString));

  //create stroke, then add to collection element      
  XElement mystroke;
  foreach (Stroke s in mystrokes)
  {
    mystroke = new XElement(xmlns + "Stroke",
      new XElement(xmlns + "Stroke.DrawingAttributes",
        new XElement(xmlns + "DrawingAttributes",
           new XAttribute("Color", s.DrawingAttributes.Color),
           new XAttribute("OutlineColor",
                          s.DrawingAttributes.OutlineColor),
           new XAttribute("Width", s.DrawingAttributes.Width),
           new XAttribute("Height", s.DrawingAttributes.Height))));

    //create points separately then add to mystroke XElement
    XElement myPoints = new XElement(xmlns + "Stroke.StylusPoints");
    foreach (StylusPoint sp in s.StylusPoints)
    {
      XElement mypoint = new XElement(xmlns + "StylusPoint",
        new XAttribute("X", sp.X.ToString()),
        new XAttribute("Y", sp.Y.ToString()));
      //add the new point to the points collection of the stroke
      myPoints.Add(mypoint);
    }
    //add the new points collection to the stroke
    mystroke.Add(myPoints);
    //add the stroke to the collection
    XMLStrokes.Add(mystroke);
  }
  return XMLStrokes;
}
XAML を作成するための名前空間の使い方に注目してください。Silverlight 2 XMLReader では、XAML を後でオブジェクトに読み込むために、この名前空間を XAML 要素のルートで使用する必要があります。

サーバー側で分析する
これで、結果を文字列として返すサービス操作に対して、文字列をパラメータとして渡せるようになりました。この操作により使用されるメソッドは、XAML 文字列から StrokeCollection を再作成する必要があります。その後で、StrokeCollection を InkAnalyzer に送信できます。サーバー側では、Silverlight API にはアクセスできません。そのため、代わりに WPF API を使用します。
WPF にも XAMLReader.Load メソッドはありますが、WPF の StrokeCollection は Silverlight の StrokeCollection とは若干異なるため、スキーマが認識されず、XAMLReader.Load が失敗します。そこで、代わりに LINQ to XML を使用しましょう。XAML 文字列を簡単に調整し、データを個々の Stroke 要素から読み取った後で、新しい WPF Stroke オブジェクトを作成できます。
WPF の StrokeCollection と Silverlight の StrokeCollection の違いはごくわずかです。たとえば、WPF の Stroke.DrawingAtrributes には Outline Color がありません。WPF のストロークの StylusPoints には、Silverlight で使用されるピクセル値ではなく、デバイスに依存しない座標系が使用されます。これらの相違はありますが、Stroke、StrokeCollection、および StylusPoint の各クラスは、Silverlight でも WPF と同様の名前空間に含まれています。
図 15 の CreateWPFStrokeCollectionfromXAML メソッドは、LINQ to XML を使用して Stroke 要素のコレクションを作成し、それらの要素を反復処理して、それぞれに対応する新しい Stroke オブジェクトを作成します。このメソッドはもう一度 LINQ を使用して StylusPoints のコレクションを作成し、各ストロークの StylusPointsCollection を作成します。ただし、StrokeCollection の完全な再作成が行われるわけではありません。認識には DrawingAttributes (色など) が不要であるためです。これらの 2 つのメソッドは、前に説明した PresentationCore と WindowsBase API に依存します。作成された StrokeCollection は、分析を行うメソッドに渡すことができます。
private StrokeCollection CreateWPFStrokeCollectionfromXAML
  (string XAMLStrokes)
{
  //because namespace was used to create this
  // (for Silverlight to reuse the XAML),
  //you need to insert the namesace into the Descendent's parameter

  var xmlElem = XElement.Parse(XAMLStrokes);
  XNamespace xmlns = xmlElem.GetDefaultNamespace();
  StrokeCollection objStrokes = new StrokeCollection();
  //Query the XAML to extract the Strokes
  var strokes = from s in xmlElem.Descendants(xmlns+ "Stroke") select s;
  foreach (XElement strokeNodeElement in strokes)
  {
    //query the stroke to extract its StylusPoints
    var points = from p 
      in strokeNodeElement.Descendants(xmlns + "StylusPoint") select p;
    //create Stylus points collection from point element values
    StylusPointCollection pointData =
      new System.Windows.Input.StylusPointCollection();
    foreach (XElement pointElement in points)
    {
      double Xpoint = Convert.ToDouble(pointElement.Attribute("X").Value);
      double Ypoint = Convert.ToDouble(pointElement.Attribute("Y").Value);
      pointData.Add(new StylusPoint(Xpoint, Ypoint));
    }
    //create a new Stroke from the StylusPointCollection
    System.Windows.Ink.Stroke newstroke = new
       System.Windows.Ink.Stroke(pointData);
    //add the new stroke to the StrokeCollection
    objStrokes.Add(newstroke);
  }
  return objStrokes;
}
図 16 のメソッドを WCF または ASMX Web サービスで使用して、InkPresenter から StrokeCollection を受け取り、最も信憑性の高い推測文字列を返すこともできます。この機能は IAWinFX および IACore アセンブリに依存します。
public string RecognizeStrokes(string XAMLStrokes)
{ 
  try
  {
    //custom method to create WPF StrokeCollection from the string-based XAML
    var strokeColl = CreateWPFStrokeCollectionfromXAML(XAMLStrokes);
    var IA = new System.Windows.Ink.InkAnalyzer();
    IA.AddStrokes(strokeColl);
    var status = IA.Analyze();
    if (status.Successful)
      return IA.GetRecognizedString();
    else
      return "Not Recognized";
  }
  catch (Exception ex)
  {
    //trap and display errors at design time, not in production code  
    return "error:" + ex.Message;
  }
}

サービスを Silverlight クライアントにフックする
RecognizeStrokes メソッドを ASMX または WCF サービスにラップすることで、Silverlight アプリケーションから Web サービス メソッドを簡単に呼び出すことができます。私は、自分のソリューションに手書き認識機能を追加するために、WCF サービスを使用したことがあります。WCF について調べる必要がある場合は、Silverlight.net Web サイトのシンプルなクイックスタートを参照してください。Silverlight によるアクセスが可能な WCF サービスの作成方法が説明されています。
Windows ベースのアプリケーションでは、インクが描画されているときに認識を実行する機能が一般的ですが、分散アプリケーションの場合、ユーザーが明示的に認識を要求できる機能があると実用性が高まります。そこで、プロセスを開始するコントロールをデザイン画面に配置する必要があります。ボタンなどのコントロールを XAML で作成してください。そのボタンの Click イベント用のイベント ハンドラを作成する必要があります。イベント ハンドラで GetXAMLfromStrokes メソッドをトリガし、作成した XAML を認識のために Web サービスに送信します。私の Web サービスでは、この操作に Recognize という名前を付けました。返された文字列を表示する TextBlock コントロールも必要です。このコントロールには、RecoText という名前を付けました。
WCF サービスの参照を Silverlight アプリケーションに追加した場合、プロキシはサービス操作の非同期呼び出しだけを実装します。そのため、RecognizeCompleted を処理するためのメソッドが必要です (図 17 を参照)。記述されている注釈が複数行にわたる場合、強制改行も認識されます。そのため、図 18 に示すように、RecoText には TextBox の代わりに ScrollViewer コントロールを使用しました。
private void RecognizeButtonHandler(object sender, RoutedEventArgs e)
{
  StrokeCollection sc = inkP.Strokes;
  XElement sXML = StrokestoXAML(sc,true);
  string ss = sXML.ToString();
  GetRecoString(ss);

  //the next two lines are to test the validity of XAML created
  //(this would make a great unit test for this application)
  //StrokeCollection sc2 = new StrokeCollection();
  //sc2 = (StrokeCollection)System.Windows.Markup.XamlReader.Load(ss);
}

private void GetRecoString(string inkStrokes)
{
  Binding binding = new BasicHttpBinding();
  EndpointAddress endpoint = new
    EndpointAddress("http://myserver/SilverlightInkService.svc");
  var svc = new ServiceReference1.SilverlightInkServiceClient(binding,
    endpoint);
  svc.RecognizeCompleted += new
    EventHandler<ServiceReference1.RecognizeCompletedEventArgs>
    (svc_RecognizeCompleted);
  svc.RecognizeAsync(inkStrokes);
}

private void svc_RecognizeCompleted(object sender, 
  ServiceReference1.RecognizeCompletedEventArgs e)
{
  RecoText.Text = e.Result.ToString();
}
図 18 認識されたテキストの表示に ScrollViewer コントロールを使用する
テストを行いやすくするために、インクや認識されたテキストを消去するボタンを XAML ページに追加してもよいでしょう。これにより、さまざまな注釈を試すことができるようになります。名前が示すとおり、InkPresenter.Strokes.Clear はストロークを InkPresenter から削除します。

サービスを使用して注釈を保存する
このソリューションでは、注釈を保存するために Web サービスを使用することも重要です。種類 (手書きのテキスト、図形、またはその他のマークアップ) を問わず、StrokeCollection をデータベースやその他の種類のデータ ストアで保持しなければならないことはよくあります。Silverlight に用意されている IsolatedFileStorage を使用して、注釈のデータをユーザーのコンピュータに保存することもできますが、この記事では、サーバー側での保存に重点を置いて説明します。
通常は、StrokeCollection 全体を保存します。その StrokeCollection は別の InkPresenter にいつでも追加できます。既に説明したように、StrokeCollection オブジェクトはシリアル化する必要があります。これを最も簡単に行う方法は、GetXAMLfromStrokes メソッドを使用して XAML 文字列表現を作成することです。
XAML 文字列さえ作成できれば、残りの作業は他の種類のテキストをデータベースに格納する作業とまったく同じです。ただし、図形の場合は XAML が膨大な量になることがあるため、その XAML を格納するデータベース フィールドを定義する際には、そうした可能性を考慮に入れる必要があります。また、大量のデータの転送に対応できるように、クライアントとサービスの設定についても考慮してください。
WCF を使用する場合は、JavaScript Object Notation (JSON) シリアル化の使用を検討してもよいでしょう。この形式でデータをシリアル化すると、XML でシリアル化した場合よりも圧縮率が高くなります。SQL Server® 2005 以降では XML データ型も利用できますが、XML データ型の利点 (クエリの実行、インデックスの作成、データ変更が可能。スキーマもサポート) を活かせるケース以外では、予想される大量の描画データに対して nvarchar や nvarchar(MAX) などの SQL Server 型が役立ちます。
サンプル アプリケーションには、ユーザーが選択できるさまざまなイメージが用意されています。各イメージの注釈は、イメージの一意のファイル名と共にデータベースに格納されます。ユーザーがイメージを選択した際に、その注釈をデータベースから取得し、表示することができます。

注釈を格納および取得する
認識の実行時には、単一の文字列だけがサービスとの間でやり取りされています。ただし、データの格納と取得の際には、データは XAML の形で渡されます。また、注釈に関係するその他のメタデータ (作成日、ユーザー、その注釈が属するイメージやビデオへの参照など) が渡されることもあります。WCF では、こうしたさまざまなメッセージのコンポーネントは DataContracts を使用して管理されます。XAML と共にイメージのパスを格納し、指定されたファイル パスの XAML を取得するための操作を、イメージ ファイルに含まれる注釈のサンプルを使用して作成してみましょう。
この WCF サービスは、インターフェイスと属性の組み合わせを使用して操作とデータ コントラクトを定義します。図 19 では、StoreImageXAML、RetrieveImageXAML、および Recognize という 3 種類のサービス操作が定義されていることがわかります。StoreImageXAML は、ImageXAMLComposite クラスで DataContract として定義されている ImageXAMLComposite 型のパラメータを受け取ります。他の 2 つの操作は、単一の文字列を受け取って返すだけなので、はるかに単純です。サービス クラスは、認識およびデータベースとのやり取りのためのヘルパ メソッドを呼び出す、これら 3 つのメソッドを実装します。データベースとやり取りするこれらのメソッドでは、LINQ to SQL を使用しています (図 20 を参照)。
namespace SilverlightInkWCFService
{
  [ServiceContract]
  public interface ISLInk
  {
    [OperationContract]
    void StoreImageXAML(ImageXAMLComposite value);

    [OperationContract]
    string RetrieveImageXAML(string imageName);

    [OperationContract]
    string Recognize(string XAMLString);
  }

  [DataContract]
  public class ImageXAMLComposite
  {
    string imagePath;
    string xamlString;

    [DataMember]
    public string XAMLString
    {
      get { return xamlString; }
      set { xamlString = value; }
    }

    [DataMember] 
    public string ImagePath
    {
      get { return ImagePath; }
      set { ImagePath = value; }
    }
  }
}
public void StoreImageXAML(ImageXAMLComposite value)
{
  //ExistingRows, InsertRow and updateRow use LINQ to the SQL 
  //SubmitChanges
  if (ExistingRows(value.ImagePath) == true)
    insertRow(value.ImagePath, value.XAMLString);
  else
    updateRow(value.ImagePath, value.XAMLString);
}

public ImageXAMLComposite RetrieveImageXAML(string imagePath)
{
  //getXAML method performs a LINQ to SQL query 
  return getXAML(imagePath);
}

public string Recognize(string XAMLString)
{
  return RecognizeStrokes(XAMLString);
}

まとめ
この記事では、InkPresenter の作成と操作、手書き認識の実行、およびサービスを使用した注釈の XAML 表現の格納と取得について、重要な内容をすべて説明しました。Tablet PC を所有していない場合や Windows Vista を実行していない場合でも、解像度がそれほど高くはならないとはいえ、このアプリケーションを使用できます。描画にマウスを使うこともできます。
ビデオを使用するというのも、興味深い選択肢の 1 つです。Silverlight アニメーションを使用してストロークを再生することもできます。ビデオとのタイミングを合わせるのは困難ですが、おもしろい作業でもあります。
Silverlight、Silverlight SDK、Visual Studio 2008 用 Silverlight Tools Beta 2、および Expression Blend 2.5 プレビュー (June 2008) は、go.microsoft.com/fwlink/?LinkId=122132 からダウンロードできます。
MSDN® フォーラム「ノートブック型パソコン、Tablet PC、および UMPC の開発」でご支援いただいているマイクロソフトの Stefan Wick 氏に感謝します。この記事を執筆するにあたって、重要なアドバイスをいただきました。


Julia Lerman は、20 年以上ソフトウェアの構築に携わる .NET コンサルタントです。.NET コミュニティで会議講演者、執筆者、Microsoft .NET MVP、および Vermont .NET User Group のリーダーとしてよく知られています。彼女の次の書籍のタイトルは『Programming Entity Framework』です。Julia のブログは thedatafarm.com/blog で公開中です。

Page view tracker