読者です 読者をやめる 読者になる 読者になる

かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Surface DialをUWPアプリから使う

Surface Studioと同時に発表されて何かと話題のSurface Dialですが、こいつのAPIはWindows 10 AUでこっそりと入っていたみたいです。 ということで簡単にですが使い方を書いてみようと思います。

何もしないという選択肢

まず、Surface Dialですが何もしなくてもデフォルトの挙動だけで乗り切るというのもあります。 その場合は、スクロールや拡大縮小(システムのメニューでカスタマイズ可能)などの操作ができるという感じになります。

f:id:okazuki:20161111161723p:plain

ということで、何もしなくてもSurface Dialでアプリを動かすことはできます。

そうはいっても

そうはいってもオリジナルの挙動をさせたいというのがあると思います。

メニューを追加したい

Surface Dialを長押ししたときに出るメニューに自分の項目を追加することができます。 まず、Surface Dialを使うには、RadialControllerクラスのインスタンスを取得する必要があります。 これはRadialControllerクラスのCreateForCurrentViewというstaticメソッドで作成可能です。

次にメニュー項目を作成します。 メニュー項目はRadialControllerMenuItemクラスで、これもstaticなファクトリメソッドが用意されてるので、それで作ることができます。

CreateFromKnownIconメソッドは名前の通りRadialControllerMenuKnownIcon列挙体に定義されたアイコンを持ったものが作成できます。

RadialControllerMenuKnownIcon enumeration - Windows app development

RadialControllerMenuItemクラスが出来たら、Invokedイベントを購読することで選択されたときの処理を書くことができます。

コード例を以下に示します。

public sealed partial class MainPage : Page
{
    private RadialController RadialController { get; set; }
    public MainPage()
    {
        this.InitializeComponent();
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
        this.RadialController = RadialController.CreateForCurrentView();
        var item1 = RadialControllerMenuItem.CreateFromKnownIcon("Item1", RadialControllerMenuKnownIcon.InkColor);
        item1.Invoked += Item1_Invoked;
        this.RadialController.Menu.Items.Add(item1);
    }

    private async void Item1_Invoked(RadialControllerMenuItem sender, object args)
    {
        var dlg = new MessageDialog("Item1 invoked");
        await dlg.ShowAsync();
    }
}

実行してItem1を選択すると以下のようになります。

f:id:okazuki:20161111164311p:plain

RadialControllerMenuItemCreateFromIconメソッドを使うことでカスタムのアイコンを使うこともできます。

var item1 = RadialControllerMenuItem.CreateFromIcon("Item1", 
    RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/CustomIcon.png")));

押し込みを検出したい

メニューを選択すると、イベントが反応するようになります。 一番簡単なのが押し込まれたときのイベントです。 RadialControllerクラスのButtonClickedイベントを購読することで、そのメニュー選択時のクリック動作を処理することができます。

RadialControllerクラスのMenuプロパティからGetSelectedMenuItemメソッドで現在選択されてるメニューが取得できるので、それを見るか、その中のTagを見て 動作をカスタマイズするか、自分でInvokedイベントで今ステータスがどういう状況にあるのか管理するフラグでも持たせておくのがいいと思います。

先ほどのコードにButtonClickedを追加したものをいかに示します。

public sealed partial class MainPage : Page
{
    private RadialController RadialController { get; set; }
    public MainPage()
    {
        this.InitializeComponent();
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
        this.RadialController = RadialController.CreateForCurrentView();
        var item1 = RadialControllerMenuItem.CreateFromKnownIcon("Item1", RadialControllerMenuKnownIcon.InkColor);
        item1.Invoked += Item1_Invoked;
        this.RadialController.Menu.Items.Add(item1);

        this.RadialController.ButtonClicked += RadialController_ButtonClicked;
    }

    private async void RadialController_ButtonClicked(RadialController sender, RadialControllerButtonClickedEventArgs args)
    {
        var item = sender.Menu.GetSelectedMenuItem();
        var dlg = new MessageDialog($"Selected menu is {item.DisplayText}");
        await dlg.ShowAsync();
    }

    private async void Item1_Invoked(RadialControllerMenuItem sender, object args)
    {
        var dlg = new MessageDialog("Item1 invoked");
        await dlg.ShowAsync();
    }
}

実行して、Item1を選択した状態でSurface Dialを押し込むと以下のようにダイアログが出てきます。

f:id:okazuki:20161111165139p:plain

回転の検出

次に回転の検出方法です。 Surface Dialは名前のとおりくるくる回ります。

どれくらい回ったらイベントは発火するかという値の設定がRotationResolutionInDegressプロパティで設定できて、RotationChangedイベントで回転時の処理が書けます。 イベント引数から、どれくらい回転したかという情報もとることができるので、それを見て処理をする形になると思います。

クリック時と同じように、今どんなメニューが選択されてる状態なのかというのもとることができます。

public sealed partial class MainPage : Page
{
    private RadialController RadialController { get; set; }
    public MainPage()
    {
        this.InitializeComponent();
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
        this.RadialController = RadialController.CreateForCurrentView();
        var item1 = RadialControllerMenuItem.CreateFromKnownIcon("Item1", RadialControllerMenuKnownIcon.InkColor);
        item1.Invoked += Item1_Invoked;
        this.RadialController.Menu.Items.Add(item1);

        this.RadialController.ButtonClicked += RadialController_ButtonClicked;
        // 回転
        this.RadialController.RotationResolutionInDegrees = 10;
        this.RadialController.RotationChanged += RadialController_RotationChanged;
    }

    private void RadialController_RotationChanged(RadialController sender, RadialControllerRotationChangedEventArgs args)
    {
        Debug.WriteLine($"{args.RotationDeltaInDegrees} rotated");
    }

    private async void RadialController_ButtonClicked(RadialController sender, RadialControllerButtonClickedEventArgs args)
    {
        var item = sender.Menu.GetSelectedMenuItem();
        var dlg = new MessageDialog($"Selected menu is {item.DisplayText}");
        await dlg.ShowAsync();
    }

    private async void Item1_Invoked(RadialControllerMenuItem sender, object args)
    {
        var dlg = new MessageDialog("Item1 invoked");
        await dlg.ShowAsync();
    }
}

実行するとデバッグウィンドウにこんな表示がされます。

10 rotated
10 rotated
10 rotated
10 rotated
10 rotated
10 rotated
10 rotated
10 rotated
-10 rotated
-10 rotated
-10 rotated
-10 rotated
-10 rotated
-10 rotated
-10 rotated
-10 rotated

プログラムからのメニューの設定

今まではユーザーがSurface Dialを長押ししてくるくると回してメニューを選ぶという操作をしていましたが、プログラムからメニューを設定することもできます。

これはMenuプロパティのSelectMenuItemメソッドにRadialControllerMenuItemを渡すことでできます。

例えば、ボタンクリック時にメニューを切り替えるのは以下のようになります。

private void Button_Click(object sender, RoutedEventArgs e)
{
    // プログラムからメニューの選択
    this.RadialController.Menu.SelectMenuItem(
        this.RadialController.Menu.Items.First());
}

こうすることで、コンテキストに応じてメニューを動的にプログラムから切り替えるといったことができます。

未確認事項

Surface Dialの位置とか

Surface Dialをクリックしたときや回転したときのイベント引数にContractというプロパティがあります。 これは現時点でnullが入ってきてるのですが、Positionなどが取れるので、おそらく画面上にSurface Dialを置いたときの位置などが取れるのだと思います。 Surface Studioか、今後Bookなどでもファームアップデートで動くといいな。

Surface Dialを画面に置いたときの動作など

それっぽいイベントがあるのですが、上記の理由で試せていません。

まとめ

APIは単純で簡単に使えますが、マウスと競合するのでおそらくデモのようにペンと組み合わせて使うことになるデバイスだと思います。 アイデア勝負っぽいですね。