使ってみよう! Live Framework

第6回.NET Kit(3)―― ホワイトボードアプリケーション

ホワイトボードアプリケーション

前回に引き続きLive Framework SDKの.NET Kitを使用した開発です。今回は、サンプルアプリケーションとしてホワイトボードアプリケーションを作成します。アプリケーションを実行した画面は図1のようになります。機能はホワイトボードに見立てたウィンドウに黒色で手書きできるという単純なものですが、Live Frameworkを利用して手書きした内容をMeshサービス上へ保存し、同期・共有できるようにします。

図1 ホワイトボードアプリケーション
図1 ホワイトボードアプリケーション

本連載ではVisual Studio 2008と言語にVB.NETを使用します。無償のVisual Basic 2008 Express Editionでも同様の内容ものが作成できます。

WPFアプリケーションの作成

最初にホワイトボードアプリケーションの基本的な部分を作成します。Visual Studioを使用して、新しくWPFアプリケーションのプロジェクトを作成します図2⁠。

図2 WPFアプリケーションプロジェクトの作成
図2 WPFアプリケーションプロジェクトの作成

プロジェクトの作成後、Live Framework SDKの.NET Kitのライブラリの参照を追加します。詳しくは、本連載の第4回を参照してください。

InkCanvas

手書きを実現するためInkCanvasというコントロールを使用します。InkCanvasはTablet PC等でスタイラスによる入力を行い、ジェスチャーや文字認識に使用されるコントロールですが、今回はこれをホワイトボードとして利用します。

まずは、プロジェクトにデフォルトで作成されているウィンドウにInkCanvasを配置しましょう。Window1.xamlに対して、直接XAMLの記述を編集します。⁠<InkCanvas />」という記述を追加したものが図3になります。

図3 InkCanvasの追加
図3 InkCanvasの追加

この時点で実行するとウィンドウに手書き入力することができます。実際に実行して確認してみましょう。入力により画面上に追加される線は、ひとつひとつが内部ではStrokeクラスのオブジェクトとして保持されています。

コードを記述しない状態で手書き入力が可能になり便利なコントロールですが、これだけでは線の削除もできず機能的に不十分ですので、少しだけコントロールとコードを記述して機能を追加します。

追加する内容は

  • 消しゴムの選択
  • ホワイトボードのクリア

および消しゴム選択から手書き入力に戻るための

  • ペンの選択

とします。ここでは、ボタンを3個ウィンドウに配置し、ボタンクリック時に以上の各処理を行うよう実装します。

XAMLファイルを以下のように編集します。InkCanvasだけでなくボタンを追加し、Clickイベントの記述を行います。またInkCanvasをコードから参照するためMyInkCanvasという名前を付けています。

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ホワイトボード" Height="300" Width="300">
    <DockPanel>
        <StackPanel DockPanel.Dock="Bottom"  Orientation="Horizontal">
            <Button Content="ペン" Click="PenButton_Click" Width="80" Margin="2" />
            <Button Content="消しゴム" Click="EraserButton_Click" Width="80" Margin="2" />
            <Button Content="クリア" Click="ClearButton_Click"  Width="80" Margin="2" />
        </StackPanel>
        <InkCanvas Name="MyInkCanvas" />
    </DockPanel>
</Window>

続いてボタンのClickイベント処理を記述します。ソリューションエクスプローラからWindow1.xaml.vbを選択してイベント処理のコードを追加します。

ペンと消しゴムの選択はInkCanvasのEditingModeプロパティを使用し、入力動作を指定します。このアプリケーションでは、ペンと消しゴムの動作としてInkCanvasEditingMode.InkとInkCanvasEditingMode.EraseByStrokeを指定することにします。コードは次のようになります。

Private Sub PenButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    MyInkCanvas.EditingMode = InkCanvasEditingMode.Ink
End Sub

Private Sub EraserButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    MyInkCanvas.EditingMode = InkCanvasEditingMode.EraseByStroke
End Sub

このEditingModeで指定できる動作には、ほかにも線の選択や、書いた線の一部分を消す動作などがあります。

ホワイトボードのクリアは、InkCanvasが保持しているStrokeオブジェクトのコレクションをクリアします。コードは次のようになります。

Private Sub ClearButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    MyInkCanvas.Strokes.Clear()
End Sub

以上でホワイトボードとしての処理部分は完了です。実際に実行して各ボタンが機能するか確認してみましょう。

ユーザー定義のデータ

さて、ここで前回までに紹介してきたLive FrameworkのMesh Objectについて、ユーザー定義のデータを読み書きする方法について紹介します。Mesh ObjectはMeshサービス上で同期する単位でリソースのひとつでした。Mesh Objectは、Data Feedというコレクションを持ち、さらにData Feedは、Data Entryというデータを表す最少単位のリソースのコレクションを指し示すことが可能です。

Mesh上へファイルを追加する方法は、第4回で少し紹介しています。この方法を使用するとData Entryへバイナリデータを追加することが可能です。

アプリケーションでバイナリデータを扱う場合はファイル追加のときと同様に処理すればよいですが、アプリケーションで使用する独自のクラスオブジェクトなどの保存には、もう少し便利なメソッドが用意されています。それが、SetUserDataメソッドとGetUserDataメソッドです。

SetUserDataとGetUserDataメソッドは、MeshObject.ResourceとDataEntry.Resourceにあるジェネリックメソッドです。Mesh ObjectまたはData Entryに対して型引数を指定して、クラスオブジェクトやプリミティブな値(StringやIntegerなど)の設定と取得ができます。

コード例を以下に示します。単純にString型の値をMesh Objectに設定および取得するには次のようになります。

' Imports Microsoft.LiveFX.Client
' Imports System.Net

Dim loe = New LiveOperatingEnvironment
loe.Connect(New NetworkCredential("userid", "password"), New LiveItemAccessOptions(True))

' Mesh Object に文字列データの設定
Dim meshObject = New MeshObject("Sample")
meshObject.Resource.SetUserData(Of String)("文字列データ")

loe.Mesh.MeshObjects.Add(meshObject) ' Mesh へ追加

' Mesh Object から文字列データの取得
Dim str = meshObject.Resource.GetUserData(Of String)()
MessageBox.Show(str)

続いて独自のクラスを設定する場合です。クラスオブジェクトの場合は、シリアラズ可能なもの(具体的には文字列や数値として表せるもの)に限られます。クラスオブジェクトを設定・取得する例を次に示します。以下のようなクラスを設定するものとします。

Public Class UserDataClass
    ' メンバ1
    Public Foo As String

    ' メンバ2
    Private _bar As Integer
    Public Property Bar() As Integer
        Get
            Return _bar
        End Get
        Set(ByVal value As Integer)
            _bar = value
        End Set
    End Property
End Class

先ほどと同じようにこのクラスオブジェクトをMesh Objectのデータとして設定・取得するコードは次のようになります。

' Mesh Object に文字列データの設定
Dim meshObject = New MeshObject("UserDataClass")
Dim userData1 = New UserDataClass With {.Foo = "hoge", .Bar = 123}
meshObject.Resource.SetUserData(Of UserDataClass)(userData)

' Mesh Object から文字列データの取得
Dim userData2 = meshObject.Resource.GetUserData(Of UserDataClass)()
MessageBox.Show(userData2.Foo & userData2.Bar.ToString)

シリアライズ可能なものに限られますが、Live Frameworkを利用してアプリケーションのデータを保存や共有する場合、これらのメソッドは有用でしょう。本記事でもこのメソッドを使用してホワイトボードアプリケーションのデータを保存します。

Live Frameworkの利用

それでは、Live Frameworkを利用してホワイトボードアプリケーションへ入力されたStrokeオブジェクトをMesh上へ追加できるようにしてみましょう。今回作成するアプリケーションでは、InkCanvasへStrokeオブジェクトが追加されるたびに すべてのStroke情報をユーザー定義データとしてMesh Objectへ設定・更新するようにします。

LOEへ接続とMesh Objectの作成

最初にLive Operating Environment(LOE)の接続と、Stroke情報を保存するMesh Objectの作成を行います。いずれもウィンドウのLoadイベントで処理することにします。以下にコードを示します。

Private Const WhiteboardResourceType As String = "GihyoJp.SampleApplication.Whiteboard"
Private LOE As New LiveOperatingEnvironment ' LOE
Private WithEvents WhiteboardObject As MeshObject ' Mesh Object

Private Sub Window1_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
    ' LOEへ接続
    LOE.Connect(New NetworkCredential("userid", "password"), New LiveItemAccessOptions(True))

    ' Mesh Objectの取得
    Dim whiteboards = From mo In LOE.Mesh.MeshObjects.Entries _
                      Where mo.Resource.Type = WhiteboardResourceType
    If whiteboards.Count > 0 Then
        ' Mesh 内に既にホワイトボードアプリケーション用の Mesh Object がある場合
        WhiteboardObject = whiteboards.First
    Else
        ' Mesh 内にホワイトボードアプリケーション用の Mesh Object がない場合、作成
        WhiteboardObject = New MeshObject
        WhiteboardObject.Resource.Type = WhiteboardResourceType
        WhiteboardObject.Resource.Title = "whiteboard app"
        LOE.Mesh.MeshObjects.Add(WhiteboardObject)
    End If
End Sub

実用的なアプリケーションにするためには、ユーザーIDとパスワードの入力のためサインインウィンドウなどを用意する必要があるでしょうが、ここではコードに直接記述して済ませています。

上記コードでは、LOEに接続後、ホワイトボードアプリケーションのStroke情報を格納するためのMesh ObjectをMeshから取得しています。ない場合は新たにMesh Objectを作成しています。このときMesh ObjectとのResource.Typeには ほかのアプリケーションと重ならないような適当な値を指定しています。

Stroke情報の保存

InkCanvas.Strokesプロパティ(StrokeCollection型)のSaveメソッドを使用すると、StrokeのコレクションをISF(Ink Serialized Format)という形式で保存することができます。

Using fs = New System.IO.FileStream("strokes", IO.FileMode.Create, IO.FileAccess.Write)
    MyInkCanvas.Strokes.Save(fs)
End Using

ただ、この形式はバイナリデータですので少し工夫してStrokeコレクションをXML形式で保存できるようにします。実際に使用するコードは次のようになります。

Using ms = New System.IO.MemoryStream
    XamlWriter.Save(MyInkCanvas.Strokes, ms)
    Dim strokes = System.Text.Encoding.UTF8.GetString(ms.ToArray)
End Using

入力されたStroke情報をMeshへ追加・更新するタイミングは、InkCanvasへStrokeが追加または削除されたときとします。これらのタイミングはStrokeCollectedイベントとStrokeErasedイベントで知ることができます。

XML形式にする部分をメソッド化しMesh Objectの更新処理を追加したコードを以下に示します。

Private Sub SetStrokes()
    Using ms = New System.IO.MemoryStream
        XamlWriter.Save(MyInkCanvas.Strokes, ms)
        Dim strokes = System.Text.Encoding.UTF8.GetString(ms.ToArray)
        WhiteboardObject.Resource.SetUserData(Of String)(strokes)
    End Using
    WhiteboardObject.Update()
End Sub

Private Sub MyInkCanvas_StrokeErased(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyInkCanvas.StrokeErased
    SetStrokes()
End Sub

Private Sub MyInkCanvas_StrokeCollected(ByVal sender As Object, ByVal e As System.Windows.Controls.InkCanvasStrokeCollectedEventArgs) Handles MyInkCanvas.StrokeCollected
    SetStrokes()
End Sub

クリアボタンをクリックしたときには上記のイベントは発生しないため、クリアボタンをクリックしたときの処理にもSetStrokesを呼ぶように変更しておきましょう。

以上で、ホワイトボードのStroke情報をMesh Objectへ保存できるようになりました。次は、Mesh Objectの内容からStroke情報を復元できるようにします。

Stroke情報の復元

Mesh ObjectにはXML形式の文字列としてStroke情報を保存していたので、文字列からStroke情報へ復元し、InkCanvasへ反映する作業が必要になります。以下にそのコードを示します。

Private Sub GetStrokes()
    WhiteboardObject.Load()

    Dim xml = WhiteboardObject.Resource.GetUserData(Of String)()
    If xml IsNot Nothing Then
        Dim strokes = TryCast(XamlReader.Parse(xml), StrokeCollection)
        If strokes IsNot Nothing Then
            MyInkCanvas.Strokes.Clear()
            MyInkCanvas.Strokes.Add(strokes)
        End If
    End If
End Sub

明示的にMesh ObjectのLoadメソッドを呼び、最新の内容を取得しています。そして取得した文字列の値は、XamlReader.ParseメソッドおよびTryCastを使用してStrokeCollectionに変換しInkCanvasのStrokeコレクションに追加しています。

Stroke情報を復元するタイミングはアプリケーションの起動時とMesh Objectがほかのデバイスからなど外部要因で更新された場合です。起動時に処理するにはウィンドウのLoadイベントの最後にGetStrokesメソッドの呼び出しを追加ます。Mesh Objectの更新通知を受信するには、Mesh ObjectのChangeNotificationReceivedイベントを使用します。

Private Sub WhiteboardObject_ChangeNotificationReceived(ByVal sender As Object, ByVal e As System.EventArgs) Handles WhiteboardObject.ChangeNotificationReceived
    GetStrokes()
End Sub

更新通知の受信については前回に紹介していますので、そちらも参照して、より改良してみてください。


以上で今回作成するアプリケーションは完成です。実行してみて動作を確認してみてください。アプリケーションを終了し、再度起動したら前回のホワイトボードの内容が復元されましたか? 複数の場所でアプリケーションを起動するとホワイトボードの内容が同期されましたか?

おわりに

最後に作成したアプリケーションの動作について改良点などを補足しておきます。

実際にアプリケーションを実行してみて気づいたかもしれませんが、Mesh ObjectのUpdateやLoadメソッドなどネットワークの通信部分をすべて同期呼び出ししているため、時間がかかり快適なユーザーインターフェースとは言えません。UpdateやLoadメソッドには非同期呼び出しも用意されています。たとえばUpdateの場合はUpdateAsyncメソッドがあります。

WhiteboardObject.UpdateAsync("state") ' 非同期呼び出し

非同期呼び出しをした場合Object型の引数を指定できます。アプリケーションで何らかの状態を管理する必要がある場合に利用します。非同期処理が完了を知るにはイベントを使用します。

Private Sub WhiteboardObject_UpdateCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs) Handles WhiteboardObject.UpdateCompleted
    Console.WriteLine(e.UserState)
End Sub

非同期処理が完了するまで次の非同期処理のメソッドを呼ぶことはできません。そのため、今回のアプリケーションを単純にUpdate等のメソッドを非同期用のメソッドに置き換えるだけでは動きません。その点も踏まえてぜひ改良してみてください。

また、データの同期・共有はしていますが、現在はユーザー間の共有はできません。よりおもしろいアプリケーションにするには、ユーザー間でのデータ共有が重要になってきます。この点については次回以降でふれたいと思います。

おすすめ記事

記事・ニュース一覧