デスクトップ アプリを Surface Dial に対応させる

MVP Global Summit で渡米した際、Microsoft のオールインワン PC “Surface Studio” とともに発表された “Surface Dial” を入手できたので、さっそくアプリから使ってみました。 アプリでの対応は簡単で、むしろ「どう使わせるか」のアイディア勝負になるデバイスという印象です。

発表時の映像での「Surface Studio の画面に置いて使う」インパクトが強いですが、デバイス自体は BLE (Bluetooth 4.0) で接続するもので、Surface 以外でも使用できます。 その場合は画面ではなく、机に置いて使う形になります。

UWP アプリから使う

UWP API の Windows.UI.Input.RadialController という型を使用して、メニュー項目の操作や回転・クリック (押し込み) イベントを購読します。 エントリー ポイントは RadialController.CreateForCurrentView() という静的メソッドで、これが RadialController のインスタンスを取得する唯一の方法です。

UWP、および Dial の基本的な実装方針に関しては下記エントリーが詳しいです。

http://blog.okazuki.jp/entry/2016/11/11/171706
https://blogs.msdn.microsoft.com/shintak/2016/11/15/dialprogramming/

WPF アプリから使う (基本)

WPF アプリ (.NET Framework アプリ) でも、UWP アプリと同じ要領で Surface Dial に対応させることができます。 UWP API である RadialController を使用することになるため、プロジェクトに WindowsRuntime を参照させる必要があります。

UWP アプリにおける RadialController のエントリー ポイントが View であるように、デスクトップ アプリの場合はウィンドウ ハンドルです。 つまり、現時点において Surface Dial のコントローラーはウィンドウ単位で作る必要がある、ということです。

さっそくですが、自分のプロダクト (KanColleViewer) を Surface Dial を対応させ、UI を操作できるようにしました。 タブを切り替えるだけの単純な機能ですが、Surface Dial の回転イベントを拾って切り替えています。 要点は下記 2 点だけです。

Windows Forms の場合も同じです。 Microsoft のサンプルにウィンドウ ハンドルを使って RadialController のインスタンスを取得する方法が記載されています。

WPF アプリから使う (つらい)

RadialController に追加するメニュー項目は、既定で用意されているアイコンから選択するか、もしくは独自のアイコンを設定することができます。 先述の KanColleViewer の例は既定のアイコンから選択しました。

  • .CreateFromKnownIcon(): 既定のアイコンを RadialControllerMenuKnownIcon から選択
  • .CreateFromIcon(): 独自のアイコンを IRandomAccessStreamReference で指定

つまり、独自アイコンのメニュー項目を追加したい場合は、デスクトップ アプリの場合でも IRandomAccessStreamReference を用意する必要があります。

Microsoft のサンプルにあるとおり、ファイル システム上に存在する画像ファイルをアイコンとして使用する場合は、StorageFile.GetFileFromPathAsync(path)StorageFile を取得し、RandomAccessStreamReference.CreateFromFile(storage) で OK です。

一方で、アセンブリ内にリソースとして配置した画像ファイルをアイコンとして使用する場合は、少々面倒な操作が必要になります。 先にコードを示しておきます。

ご覧の通り、アイコン画像リソースの URI から Stream を作成し、バイト配列に読み込み、それを UWP の InMemoryRandomAccessStream に書き込む、という手順です。 ストリームへの書き込みで非同期操作が出てくるため、たかだかメニュー項目の作成で async/await している微妙っぷり (まあ、.AsTask().Wait() してしまえば…)。

上記コードを動かす場合、Windows.Foundation.IAsyncOperation<T> を await するために System.Runtime.WindowsRuntime.dll の参照が必要です。 ちなみに同アセンブリには .NET Framework の Stream を IRandomAccessStream にするための .ToRandomAccessStream() なる便利拡張メソッドがあるのですが、その方法だとアイコンが表示されませんでした。つらい。

UWP であれば RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/Item3.png")) のように URI を指定して一発で終わりなんですが、WPF アプリの場合はこの方法でしか追加できませんでした (ぐらばく調べ。もっといい方法があったら教えてください…)。 AppX 化したデスクトップ アプリなら ms-appx:// な URI でいけるんじゃないか、と思ってますが未確認。

おわりに

場合によって若干面倒なことになるものの、デスクトップ アプリでも簡単に Surface Dial に対応させることができる、ということを紹介しました。

重ねて言っておきますが、これはアイディア勝負です。 UWP でも WPF でも実装自体は簡単で、ユーザーにどれほど有用な操作を提供できるかが勝負になると思っています。 先の KanColleViewer に実装した例はあくまでサンプルであって、私自身あの機能自体が有用であるとは微塵も思っていません。

なので、近々アイディアソンをしたいな、と企んでいます。 が、その前に日本発売いつなんだろう…


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です