かずきのBlog@hatena

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

仮想化がサポートされた ItemsControl みたいなコントロール ItemsRepeater

要素の選択機能とかいらない、もっとシンプルに要素を列挙したいんです!っていうときに ItemsControl 使いますが、こいつは仮想化サポートしてないです。

WinUI に追加された ItemsRepeater は、仮想化がイケる ItemsControl みたいなやつです。

使ってみよう

使い方は ItemsSource に適当なコレクションをバインドする感じです。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace CompactSampleApp
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public IEnumerable<string> Items { get; }
        public MainPage()
        {
            this.InitializeComponent();

            Items = Enumerable.Range(1, 10000).Select(x => $"Item {x}").ToArray();
        }
    }
}
<Page
    x:Class="CompactSampleApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="using:Microsoft.UI.Xaml.Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:CompactSampleApp"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">
    <Grid
        x:Name="grid"
        Grid.Row="1"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:ItemsRepeater ItemsSource="{x:Bind Items}" />
    </Grid>
</Page>

こんな感じ。

f:id:okazuki:20190423171107p:plain

ScrollViewer にも入れることが出来ます。

<Page
    x:Class="CompactSampleApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="using:Microsoft.UI.Xaml.Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:CompactSampleApp"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">
    <Grid
        x:Name="grid"
        Grid.Row="1"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ScrollViewer>
            <controls:ItemsRepeater ItemsSource="{x:Bind Items}" />
        </ScrollViewer>
    </Grid>
</Page>

f:id:okazuki:20190423171229p:plain

Windows 10 1809 より前で動かすことがあるケース(というか今時点では、ほとんどそのケースなのかな?)では ItemsRepeaterScrollHostScrollViewer をラップしないといけないみたいです。

<controls:ItemsRepeaterScrollHost>
    <ScrollViewer>
        <controls:ItemsRepeater ItemsSource="{x:Bind Items}" />
    </ScrollViewer>
</controls:ItemsRepeaterScrollHost>

ItemTemplate や ItemTemplateSelector でデータの見た目も変えれる。

<Page
    x:Class="CompactSampleApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="using:Microsoft.UI.Xaml.Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:CompactSampleApp"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:system="using:System"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">
    <Grid
        x:Name="grid"
        Grid.Row="1"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:ItemsRepeaterScrollHost>
            <ScrollViewer>
                <controls:ItemsRepeater ItemsSource="{x:Bind Items}">
                    <controls:ItemsRepeater.ItemTemplate>
                        <DataTemplate x:DataType="system:String">
                            <Grid Margin="10">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition />
                                    <ColumnDefinition Width="Auto" />
                                </Grid.ColumnDefinitions>
                                <SymbolIcon Symbol="Accept" />
                                <TextBlock
                                    Grid.Column="1"
                                    Style="{StaticResource BodyTextBlockStyle}"
                                    Text="{x:Bind}" 
                                    Margin="10,0"/>
                                <TextBlock
                                    Grid.Column="2"
                                    Style="{StaticResource BodyTextBlockStyle}"
                                    Text="{x:Bind system:String.Format('{0}文字', Length)}" 
                                    Margin="10,0"/>
                            </Grid>
                        </DataTemplate>
                    </controls:ItemsRepeater.ItemTemplate>
                </controls:ItemsRepeater>
            </ScrollViewer>
        </controls:ItemsRepeaterScrollHost>
    </Grid>
</Page>

f:id:okazuki:20190423172117p:plain

レイアウトは StackLayout と UniformGridLayout が提供されていて、デフォルトが縦方向指定の StackLayout。 StackLayout は、縦並び横並びであんまりおもしろくないけど UniformGridLayout は WPF でいうところの WrapPanel みたいなのがさくっと作れていい感じ。

<Page
    x:Class="CompactSampleApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="using:Microsoft.UI.Xaml.Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:CompactSampleApp"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:system="using:System"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">
    <Grid
        x:Name="grid"
        Grid.Row="1"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:ItemsRepeaterScrollHost>
            <ScrollViewer>
                <controls:ItemsRepeater ItemsSource="{x:Bind Items}">
                    <controls:ItemsRepeater.Layout>
                        <controls:UniformGridLayout MinItemWidth="200"
                                                    MinColumnSpacing="10"
                                                    ItemsJustification="Center" 
                                                    Orientation="Horizontal" />
                    </controls:ItemsRepeater.Layout>
                    <controls:ItemsRepeater.ItemTemplate>
                        <DataTemplate x:DataType="system:String">
                            <Grid Margin="10" Background="LightBlue">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition />
                                    <ColumnDefinition Width="Auto" />
                                </Grid.ColumnDefinitions>
                                <SymbolIcon Symbol="Accept" />
                                <TextBlock
                                    Grid.Column="1"
                                    Style="{StaticResource BodyTextBlockStyle}"
                                    Text="{x:Bind}" 
                                    Margin="10,0"/>
                                <TextBlock
                                    Grid.Column="2"
                                    Style="{StaticResource BodyTextBlockStyle}"
                                    Text="{x:Bind system:String.Format('{0}文字', Length)}" 
                                    Margin="10,0"/>
                            </Grid>
                        </DataTemplate>
                    </controls:ItemsRepeater.ItemTemplate>
                </controls:ItemsRepeater>
            </ScrollViewer>
        </controls:ItemsRepeaterScrollHost>
    </Grid>
</Page>

f:id:okazuki:20190423172816p:plain

そのほかに

要素に対するライフサイクルイベントや、グルーピングは直接サポートされてないけどグルーピングの UI を作る方法とかがドキュメントにたくさん書いてあります。

docs.microsoft.com

まとめ

ちょっと本格的なコレクション UI 作るときや、ListView とかではマッチしないときは、これを使うことになりそう。