Windows App SDK 1.0 でデータバインディングを利用した NavigationView を実装する

やりたいこと

Microsoftチュートリアルにある「データ バインディングを使用した項目の階層の追加」を Windows App SDK で動かしたい。

docs.microsoft.com

まあ、このソースをいい感じに貼り付ければ動くとは思っていましたよ。プロジェクトを作成する前は。

やりかた

データテンプレートを Resources に作ることができないので、NavigationView に直接書くしかない。ちなみに、MVVM っぽくしたので、Model に NavigationView のアイテム要素を定義した。

ソリューションエクスプローラーはこんな感じ。

f:id:takunology:20220207190328p:plain

MainWindow.xaml の中身

<Window
    <!-- 色々書いてある部分 -->
    xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
    xmlns:model="using:SampleApp.Models"
    mc:Ignorable="d">

    <Grid>
        <muxc:NavigationView x:Name="navview"
        MenuItemsSource="{x:Bind ViewModel.Categories, Mode=OneWay}"     
        ItemInvoked="{x:Bind ViewModel.OnItemInvoked}" 
        Expanding="{x:Bind ViewModel.OnItemExpanding}" 
        Collapsed="{x:Bind ViewModel.OnItemCollapsed}"
        PaneDisplayMode="Left">
        <!-- ここに直接書き込む -->
            <muxc:NavigationView.MenuItemTemplate>
                <DataTemplate x:DataType="model:NavViewCategory">
                    <muxc:NavigationViewItem Content="{x:Bind Name}" MenuItemsSource="{x:Bind Children}"/>
                </DataTemplate>
            </muxc:NavigationView.MenuItemTemplate>
        </muxc:NavigationView>
    </Grid>
</Window>

コードビハインドに VIewModel のプロパティを宣言しておく。

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

ViewModel.cs に関してはチュートリアルに書いてあった ObservableCollection を実装するので省略。この記事を参考にする場合はジェネリックの型名が <NavViewCategory> になるので注意。

NavViewCategory.cs というモデルをつくって、そこでアイテム要素を定義しておく。

public class NavViewCategory
{
    public String Name { get; set; }
    public String CategoryIcon { get; set; }
    public ObservableCollection<NavViewCategory> Children { get; set; }
}

これで実行すると、NavigationView を動かすことができる。あとはページ遷移を実装すればいい感じになりそう。

f:id:takunology:20220207183211g:plain

たいへんだったこと

チュートリアルのほうは UWP 向けなので、XAML 要素が <Page> になっているが、Windows App SDK でプロジェクトを作成すると、XAML 要素は <Window> になっている。なので、データテンプレートを作るときからちょっと工夫しないといけなかった。

要素でデータテンプレートを定義するときはこのように書くらしい。

<Window.Resources>
    <DataTemplate x:Key="NavigationViewMenuItem" x:DataType="model:NavViewCategory">
        <muxc:NavigationViewItem Content="{x:Bind Name}" MenuItemsSource="{x:Bind Children}"/>
    </DataTemplate>
</Window.Resources>

でも Windows App SDK では弾かれる。

f:id:takunology:20220207184214p:plain

どうやら、Windows App SDK では <Window.Resources> が廃止になったみたい。

github.com

なので、チュートリアルにあった StaticResource からテンプレートを読み込めなくて辛い。。。

色々調べていたら、App.xaml に定義する方法があるみたいで、<ResourceDictionary> の中に追記していくといいみたい。

f:id:takunology:20220207184530p:plain

でもこれで実行しようとしても、こんなエラーが表示されて実行できない。

the xaml binary format (xbf) generator reported syntax error '0x09c4' : property not found

結局、チュートリアルにあった StaticResource は使えそうにないので他の方法を探すしかなかった。 何もわからないまま、しばらくぼーっとしていると StaticResourceMenuItemTemplate に対して読み込んでいたのをきっかけに、「もしかしたら <MenuItemTemplate> みたいな要素があるのでは?」とひらめいた。そして、データテンプレートを <muxc:NavigationView.MenuItemTemplate> で挟めばいいことに気づき、上記のような手法を用いたところ実行できるようになった。

Windows App SDK (WinUI3) に関する記事が少なくて、GitHub Issue を見たりしていたけどこのようなケースが見当たらなかった。結局これだけのために半日くらい消費した挙げ句、疲れてしまった。。。