かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPF on .NET Core で XAML Islands を使ってみよう

Windows 10 19H1 で XAML Islands が使えるようになりました。 .NET Core 3.0 (まだプレビュー)から使ってみましょう。

WPF プロジェクト (.NET Core) の作成

VS 2019 に .NET Core 3.0 が入った Windows 10 19H1 の環境でさくっと作れます。 作ったら NuGet から Microsoft.Toolkit.Wpf.UI.Controls のパッケージを追加しましょう。バージョンは 6 系から .NET Core 3.0 をサポートしています。まだ .NET Core 3.0 がプレビューなので、このパッケージもプレビューになります。プレビューをダウンロードするように設定しておきましょう。

f:id:okazuki:20190510035521p:plain

マニフェストファイルの作成

プロジェクトにマニフェストファイルを追加します。

f:id:okazuki:20190510035612p:plain

マニフェストファイルの assembly タグの下の compatibility タグの下の application タグの下に supportOS タグがコメントで書いてあります。

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
  <application>
    <!-- A list of the Windows versions that this application has been tested on
         and is designed to work with. Uncomment the appropriate elements
         and Windows will automatically select the most compatible environment. -->

    <!-- Windows Vista -->
    <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->

    <!-- Windows 7 -->
    <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->

    <!-- Windows 8 -->
    <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->

    <!-- Windows 8.1 -->
    <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->

    <!-- Windows 10 -->
    <!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
  </application>
</compatibility>

Windows 10 のやつをコメントアウトして、さらに maxversiontested タグを追加して対象バージョンを 19H1 以降と明記しておきましょう。これをしないと XAML Islands のライブラリーがサポートしてない OS で動かしてない?って例外吐きます。

<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
<maxversiontested Id="10.0.18362.0"/>

そして assembly タグの下に以下のような DPI Aware に関する記述を追加します。

<application xmlns="urn:schemas-microsoft-com:asm.v3">
  <windowsSettings>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
    <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
  </windowsSettings>
</application>

これをしないと、Surface Pro や Book みたいないいディスプレイでの見た目が小さくなります。

XAML Islands の初期化

初期化処理を追加します。Program.cs ファイルを新規で作って以下のように書きましょう。

using Microsoft.Toolkit.Win32.UI.XamlHost;
using System;
using System.Collections.Generic;
using System.Text;

namespace WpfApp8
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            using (var xaml = new XamlApplication())
            {
                var app = new App();
                app.InitializeComponent();
                app.Run();
            }
        }
    }
}

XamlApplication が、いい感じに初期化とかしてくれます。

そして、プロジェクトのプロパティでエントリーポイントを先ほど作成した Program クラスにして、マニフェストファイルも先ほど作成されたものになってるか確認します。

f:id:okazuki:20190510040243p:plain

使ってみよう

では、XAML 書いてみましょう。InkCanvas コントロールはラッパーコントロールが提供されてるので使いやすいので、それを使います。

<Window x:Class="WpfApp8.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp8"
        xmlns:toolkit="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Button Content="Recognize"
                Click="Button_Click" />
        <toolkit:InkCanvas Grid.Row="1" x:Name="ink" />
    </Grid>
</Window>

toolkit 名前空間を追加して、その下にある InkCanvas を置いてます。コード日ハンドに以下のようなコードを書いてみました。InkCanvas に書かれた文字を認識しています。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Windows.UI.Input.Inking.Analysis;

namespace WpfApp8
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            _inkAnalyzer = new InkAnalyzer();
        }

        private readonly InkAnalyzer _inkAnalyzer;
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            _inkAnalyzer.ClearDataForAllStrokes();
            _inkAnalyzer.AddDataForStrokes(ink.InkPresenter.StrokeContainer.GetStrokes());
            var r = await _inkAnalyzer.AnalyzeAsync();

            if (r.Status == InkAnalysisStatus.Updated)
            {
                var words = _inkAnalyzer.AnalysisRoot.FindNodes(InkAnalysisNodeKind.InkWord);
                Debug.WriteLine(words.Count());
                var texts = words.OfType<InkAnalysisInkWord>().Select(x => x.RecognizedText);
                MessageBox.Show(string.Concat(texts));
            }
        }
    }
}

InkAnalyzer は Windows Runtime の API です。さくっと使えてしまっていますが、これは Xaml Islands の NuGet パッケージが Microsoft.Windows.SDK.Contracts の NuGet パッケージを参照しているので使えるようになっています。

f:id:okazuki:20190510040618p:plain

1 つ前の記事で書いた内容ですね。

blog.okazuki.jp

動かしてみよう

普通にデバッグして実行してみます。

f:id:okazuki:20190510040815p:plain

いい感じに動いてます。いいね。

まとめ

ということで最近は UWP じゃないと出来ないことが減ってきています。 通知みたいに、msix にパッケージングされて識別子が割り当てられないと使えない API もありますが、そうじゃないものは結構呼べます。

コントロールに関しても UWP 向けに追加されているものを、UWP 以外で使う方法も整備されてきています。 デスクトップアプリという観点でいうと全部 UWP で作る必要性はあまりなさそうですね(HoloLens や Surface Hub とかで動かしたいなら UWP になりますが)