💬

WinUI 3 (Project Reunion 0.5) がリリースしてました!

2021/03/30に公開

3 月後半にリリースすると言われていた Windows UI Library 3.0 ですが、もう 3 月も最終週だけどまだかな?と思って朝の情報収集をしてるときに GitHub を覗きにいったらリリースされていました!
8 時間前に! (向こうは日曜やぞ…)

https://github.com/microsoft/microsoft-ui-xaml/issues/4682

ためしてみよう

とりあえず試してみよう!Visual Studio 2019 16.10 (Preview) の方が WinUI サポートに関しては高機能なので、そちらを使ってやっていきます。Project Reunion 0.5 の拡張機能を入れれば基本的には準備完了です。

以下のようなプロジェクトテンプレートが追加されます。

Project Reunion 0.5 の時点での注意点の 1 つとして、MSIX でパッケージングされたアプリケーションのみがサポートという点があります。将来的には MSIX でパッケージングしなくても使えるようになる予定ですが、現時点で WinUI 3.0 を使おうと思ったら MSIX によるパッケージングは必須です。

プロジェクトを新規作成すると、.NET 5 のプロジェクトと MSIX のパッケージングプロジェクトの 2 つが作成されます。.NET 5 の方のプロジェクトを見ると、以下のような感じになっています。TFM が net5.0-windows10.0.19041.0 になっていたり、PackageReferenceMicrosoft.ProjectReunion を参照していたりと完全に WinUI が Project Reunion の一部という感じになっていますね!

App1.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
    <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
    <RootNamespace>App1</RootNamespace>
    <ApplicationManifest>app.manifest</ApplicationManifest>
    <Platforms>x86;x64;arm64</Platforms>
    <RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.ProjectReunion" Version="0.5.0" />
    <PackageReference Include="Microsoft.ProjectReunion.Foundation" Version="0.5.0" />
    <PackageReference Include="Microsoft.ProjectReunion.WinUI" Version="0.5.0" />
    <Manifest Include="$(ApplicationManifest)" />
  </ItemGroup>
</Project>

そのまま実行をすると、以下のようなウィンドウが起動します。私は Windows の設定をダークテーマにしているのでアプリもそれに合わせて黒になっています。何も考えなくてもテーマ対応できているので素敵です。

寄り道

少し寄り道をしたいと思うのですが、App.xaml.cs が基本的にエントリーポイントなのですが、実際には裏で Main メソッドが生成されています。具体的には obj\x86\Debug\net5.0-windows10.0.19041.0\App.g.i.cs にありました。Main メソッドの定義のあるクラスだけ抜き出すとこんな感じでした。

App.g.i.cs
#if !DISABLE_XAML_GENERATED_MAIN
    /// <summary>
    /// Program class
    /// </summary>
    public static class Program
    {
        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.UI.Xaml.Markup.Compiler"," 0.0.0.0")]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.STAThreadAttribute]
        static void Main(string[] args)
        {
            global::WinRT.ComWrappersSupport.InitializeComWrappers();
            global::Microsoft.UI.Xaml.Application.Start((p) => {
                var context = new global::Microsoft.System.DispatcherQueueSynchronizationContext(global::Microsoft.System.DispatcherQueue.GetForCurrentThread());
                global::System.Threading.SynchronizationContext.SetSynchronizationContext(context);
                new App();
            });
        }
    }
#endif

STAThreadAttribute がついているなぁということと、ComWrappersSupport.InitializeComWrappers() の呼び出しとかがあるので、まぁ裏側は COM の世界なんだよなぁとかいうことがわかりますね。あと SynchronizationContext には DispatcherQueueSynchronizationContext が設定されていることも確認できました。

なんとなく気になったのでチェックしただけですが、こういうものを見るのも楽しいですね。

App クラスから Suspending がなくなった

Project Reunion 0.5 preview までは Win32 アプリのプロジェクトテンプレートを作っても Suspending イベントを購読していました。Win32 アプリなので、サスペンドしないと思うんだけどなぁと思っていたら、0.5 のリリースタイミングで Suspending イベントを購読するコードがまるっと無くなっていたので App クラスがとてもシンプルになっていました。

App.cs
// 余分な using やコメントや宗教上受け入れられない変数の命名規則などは編集しています。
using Microsoft.UI.Xaml;

namespace App1
{
    public partial class App : Application
    {
        public App()
        {
            this.InitializeComponent();
        }

        protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
        {
            window = new MainWindow();
            window.Activate();
        }

        private Window window;
    }
}

いいね。すっきり。

WPF/UWP との主な違い

とりあえず最初にやる人達がハマるであろう大きな違いだけど紹介したいとおもいます。WinUI 3 では Window クラスに DataContext プロパティがありません。

Window クラスは以下のように定義されています。

えっ?じゃぁ ViewModel とかとどうやってバインドするの??という疑問がわくと思いますが、Window 内に置かれるコントロール類は DataContext プロパティを持っているのでそこで設定すれば従来通り {Binding Xxxxx} のようにバインドできます。

もしくは、コンパイル時データバインディングもサポートしているので、MainWindow のプロパティに適当に ViewModel を設定して {x:Bind ViewModel.Xxxxxx} のようにしてコンパイル時データバインディングを使うことも出来ます。基本的にはコンパイル時データバインディングを使って、{Binding} のほうは必要最低限にしておくのが性能の面では有利になるのかなと思います。

ReactiveProperty を試してみる

最後に ReactiveProperty を試してみて、この記事は終わりにしたいと思います。SynchronizationContext が設定されていることは先ほど確認したので ReactiveProperty は普通に WinUI 3 でも使うことが出来ます。追加するパッケージは ReactiveProperty で大丈夫です... と言いたいところですが arm64 系でエラーが出てしまいました…。

原因は深追いしていませんが ReactiveProperty を使う場合は今のところプロジェクトファイルから ARM64 系の定義を消す必要があります。あと、CompositeDisposable 等を使おうとすると Windows 10.0.19041.0 以上じゃないとダメという警告が出たりしたので、最終的には以下のようにプロジェクトファイルを変更しました。

App1.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
    <RootNamespace>App1</RootNamespace>
    <ApplicationManifest>app.manifest</ApplicationManifest>
    <Platforms>x86;x64</Platforms>
    <RuntimeIdentifiers>win10-x86;win10-x64</RuntimeIdentifiers>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.ProjectReunion" Version="0.5.0" />
    <PackageReference Include="Microsoft.ProjectReunion.Foundation" Version="0.5.0" />
    <PackageReference Include="Microsoft.ProjectReunion.WinUI" Version="0.5.0" />
    <PackageReference Include="ReactiveProperty" Version="7.8.3" />
    <Manifest Include="$(ApplicationManifest)" />
  </ItemGroup>
</Project>

あとはいつも通り VM を用意して

using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System;
using System.Linq;
using System.Reactive.Linq;

namespace App1
{
    public class MainWindowViewModel
    {
        public ReactiveProperty<string> Input { get; } = new ReactiveProperty<string>();

        private ReadOnlyReactivePropertySlim<string> output;
        public ReadOnlyReactivePropertySlim<string> Output => output ??= Input
            .Delay(TimeSpan.FromSeconds(3))
            .Select(x => x?.ToUpperInvariant())
            .ObserveOnUIDispatcher()
            .ToReadOnlyReactivePropertySlim();
    }
}

Window クラスのプロパティに定義して。

MainWindow.xaml.cs
using Microsoft.UI.Xaml;

namespace App1
{
    public sealed partial class MainWindow : Window
    {
        private MainWindowViewModel ViewModel { get; } = new MainWindowViewModel();
        public MainWindow()
        {
            this.InitializeComponent();
        }
    }
}

適当にバインドします。

MainWindow.xaml
<Window
    x:Class="App1.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:local="using:App1"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid RowDefinitions="Auto,Auto">
        <TextBox Text="{x:Bind ViewModel.Input.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Grid.Row="1" Text="{x:Bind ViewModel.Output.Value, Mode=OneWay}" />
    </Grid>
</Window>

実行するとちゃんと動きました!

まとめ

とりあえず、サポート有りの製品版でも使える形で Windows UI Library 3.0 こと Project Reunion 0.5 が公開されました。

個人的に気になっている技術なので、今後も追いかけていこうと思っています。

Microsoft (有志)

Discussion