Surface Dial WPF Implement Memo

WPF にSurface Dial を対応させるにはいくつかの手順が必要である。基本的な方法は、Win32 アプリケーションのインターフェースを使ってCOMオブジェクトを作成し、そこからRadialContoller のオブジェクトを使うという形。

Windows Runtime の参照

WPFでWindows Runtime を扱うために、まずはWindows Runtime の参照が必要となる。

 <Reference Include="Windows.winmd">
    <HintPath>$(MSBuildProgramFiles32)\Windows Kits\10\UnionMetadata\Windows.winmd</HintPath>
</Reference>

Comオブジェクト取得のためのインターフェース作成

続けて、Radial Contoller と RadialControllerConfigration インスタンスを取得するための、
Comオブジェクトを取得するためのインターフェースを準備する

ここはMicrosoft のサンプルが参考になる。

https://github.com/Microsoft/Windows-classic-samples/tree/master/Samples/RadialController

このインターフェースは再利用可能なので、1度作っておけばあとは楽になります。

IDesktopRadialController.cs

 [Guid("1b0535c9-57ad-45c1-9d79-ad5c34360513")]
[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface IDesktopRadialController
{
    RadialController CreateForWindow(IntPtr hWnd, [In] ref Guid iid);
}

[Guid("787cdaac-3186-476d-87e4-b9374a7b9970")]
[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface IDesktopRadialControllerConfiguration
{
    RadialControllerConfiguration GetForWindow(IntPtr hWnd, [In] ref Guid iid);
}

アプリからのインターフェースを経由したWindows Rutimeの利用

利用時には初めに、インターフェース経由でインスタンスを取得し、APIを利用。

DesktopRadialController.cs

 public static class DesktopRadialController
{
    public static RadialController Create(IntPtr hWnd)
    {
        var controller =
            (IDesktopRadialController)WindowsRuntimeMarshal.GetActivationFactory(typeof(RadialController));
        var iid = typeof(RadialController).GetInterface("IRadialController").GUID;
        return controller.CreateForWindow(hWnd, ref iid);
    }
}

public static class DesktopRadialControllerConfiguration
{
    public static RadialControllerConfiguration Create(IntPtr hWnd)
    {
        var configration = (IDesktopRadialControllerConfiguration)WindowsRuntimeMarshal.
            GetActivationFactory(typeof(RadialControllerConfiguration));
        var iid = typeof(RadialControllerConfiguration).GetInterface("IRadialControllerConfiguration").GUID;
        return configration.GetForWindow(hWnd, ref iid);
    }
}

Dial の機能の利用

実際にDaial の機能を使う方法は、UWPでの実装と大きくは変わらない。

初めに標準メニューに対するカスタマイズ

 // ウィンドウ ハンドルから RadialController と RadialControllerConfiguration のインスタンス取得
var controller = DesktopRadialController.Create(source.Handle);
var configuration = DesktopRadialControllerConfiguration.Create(source.Handle);

// 既定メニューのうちアプリで使いたいものを選んで設定
configuration.SetDefaultMenuItems(new[] { RadialControllerSystemMenuItemKind.Volume, });

// アプリ独自メニューを追加
controller.Menu.Items.Add(
    RadialControllerMenuItem.CreateFromKnownIcon(
        "Tab", 
        RadialControllerMenuKnownIcon.Scroll
    )
);

メニュー項目をカスタマイズする場合のアイコンの処理

 // リソースに置かれた画像ファイルからアイコンを取得する
// デフォルトのアイコンを使う場合: CreateFromKnownIcon()
// オリジナルの画像ファイルを使う場合: CreateFromIcon() , ソースは IRandomAccessStreamReference
// 以下の実装には IAsyncOeration のために System.Runtime.WindowsRuntime.dllの参照が必要

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)
{
    // Get Stream from IconURL
    var resourceInfo = Application.GetResourceStream(iconUri);
    if (resourceInfo == null) throw new ArgumentException("Resource not found.", nameof(iconUri));

    using (var stream = resourceInfo.Stream)
    {
        // Get Byte Allay from Strream
        var array = new byte[stream.Length];
        stream.Read(array, 0, array.Length);

        using (var randomAccessStream = new InMemoryRandomAccessStream())
        {
            // Wrote byte allay to UWP Stream
            await randomAccessStream.WriteAsync(array.AsBuffer());
            return RadialControllerMenuItem.CreateFromIcon(
                displayText, 
                RandomAccessStreamReference.CreateFromStream(randomAccessStream));
        }
    }
}

続けてイベントの取得とその実装。

 // Dial の回転イベントを定義 (args.RotationDeltaInDegrees で回転角度を取ったりする)
controller.RotationChanged += (sender, args) => Action(args.RotationDeltaInDegrees);