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 点だけです。
なんと KanColleViewer は Surface Dial に対応しました!!!!111 pic.twitter.com/PS6T8C8dkv
— ぐらばく☪ (@Grabacr07) 2016年11月11日
勢い。【備忘録】Surface Dial 実装まとめ #win10jp pic.twitter.com/SnhioYoeu5
— しのぶ@窓電話エバンジェリスト (@shinoblogavi) 2016年11月15日
Windows Forms の場合も同じです。
Microsoft のサンプルにウィンドウ ハンドルを使って RadialController
のインスタンスを取得する方法が記載されています。
WPF アプリから使う (つらい)
RadialController
に追加するメニュー項目は、既定で用意されているアイコンから選択するか、もしくは独自のアイコンを設定することができます。
先述の KanColleViewer の例は既定のアイコンから選択しました。
.CreateFromKnownIcon()
: 既定のアイコンをRadialControllerMenuKnownIcon
から選択.CreateFromIcon()
: 独自のアイコンをIRandomAccessStreamReference
で指定
つまり、独自アイコンのメニュー項目を追加したい場合は、デスクトップ アプリの場合でも IRandomAccessStreamReference
を用意する必要があります。
Microsoft のサンプルにあるとおり、ファイル システム上に存在する画像ファイルをアイコンとして使用する場合は、StorageFile.GetFileFromPathAsync(path)
で StorageFile
を取得し、RandomAccessStreamReference.CreateFromFile(storage)
で OK です。
一方で、アセンブリ内にリソースとして配置した画像ファイルをアイコンとして使用する場合は、少々面倒な操作が必要になります。 先にコードを示しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
async void InitializeController() { var controller = this.CreateRadialController(); // 中略 const string iconUri = "pack://application:,,,/SampleAssembly;Component/assets/Item0.png"; var menuItem = await CreateMenuItem("Sample", new Uri(iconUri, UriKind.Absolute)); controller.Menu.Items.Add(menuItem); } static async Task<RadialControllerMenuItem> CreateMenuItem(string displayText, Uri iconUri) { var resourceInfo = Application.GetResourceStream(iconUri); if (resourceInfo == null) throw new ArgumentException("Resource not found.", nameof(iconUri)); using (var stream = resourceInfo.Stream) { var array = new byte[stream.Length]; stream.Read(array, 0, array.Length); using (var randomAccessStream = new InMemoryRandomAccessStream()) { await randomAccessStream.WriteAsync(array.AsBuffer()); return RadialControllerMenuItem.CreateFromIcon(displayText, RandomAccessStreamReference.CreateFromStream(randomAccessStream)); } } } |
ご覧の通り、アイコン画像リソースの 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 に実装した例はあくまでサンプルであって、私自身あの機能自体が有用であるとは微塵も思っていません。
なので、近々アイディアソンをしたいな、と企んでいます。 が、その前に日本発売いつなんだろう…