uniface.hub

uniface.hub

ユニフェイスの開発者ブログ


Title 【WPF】UI仮想化の指定方法
  • 2022年10月12日
  • 角谷俊樹
【WPF】UI仮想化の指定方法

はじめに

こんにちは、6月より入社しました角谷です。
業務でUI仮想化に触る機会があったのでまとめてみました。

UI仮想化とは

数十万件のような多くのデータを扱うデータテーブルがある場合、それを一回で描画させようとするととても重くなってしまいます。このとき全て描画させる必要はなく、ユーザーに見えている箇所のみ描画させたいときに用いられるのがUI仮想化です。

下記、私なりのイメージとなります。

UI仮想化設定指定

具体的にXAMLのListViewコントロールを使って説明していきます。
ListViewは規定で有効になっていますが今回は明示的に指定していきます。
下記をListViewに指定する必要があります。

// 有効化
VirtualizingStackPanel.IsVirtualizing = "True"

// 無効化
VirtualizingStackPanel.IsVirtualizing = "False"
<!--MainWindow.xaml-->
<Window x:Class="ListViewApp.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:ListViewApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ListView
            x:Name="ListViewTable"
            VirtualizingStackPanel.IsVirtualizing = "True"> ←**指定**
            <ListView.View>
    ・
    ・
    ・
            </ListView.View>
        </ListView>
    </Grid>
</Window>

有効、無効のときどのくらい違う?

1万件データを用意してUI仮想化を有効のときと無効のときで
どのくらい処理が重たいか比較してみました。

無効の場合はカーソルについていけてないぐらい重いですね。。

有効の場合

無効の場合

ソースコード

<!--MainWindow.xaml-->
<Window x:Class="ListViewApp.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:ListViewApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ListView
            x:Name="ListViewTable"
            VirtualizingStackPanel.IsVirtualizing = "True"> ←**指定**
            <ListView.View>
                <GridView>
                    <GridViewColumn >
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox Margin="5,0"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn
                        Width="250"
                        DisplayMemberBinding="{Binding Id}"
                        Header="id" />
                    <GridViewColumn
                        Width="250px"
                        DisplayMemberBinding="{Binding Name}"
                        Header="name" />
                    <GridViewColumn
                        Width="250px"
                        DisplayMemberBinding="{Binding Age}"
                        Header="age" />
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>
<!--MainWindow.xaml.cs-->
using System.Collections.Generic;
using System.Windows;

namespace ListViewApp
{
    public partial class MainWindow : Window
    {
        private List<Customer> customers;
        public MainWindow()
        {
            InitializeComponent();
            customers = new List<Customer>();

            for (int i = 0; i < 10000; i++)
                customers.Add(new Customer(i, $"たろう{i}", i));

            ListViewTable.ItemsSource = customers;
        }
    }
}
<!--Customer.cs-->
namespace ListViewApp
{
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }

        public Customer(int id, string name, int age)
        {
            Id = id;
            Name = name;
            Age = age;
        }
    }
}

【おまけ】スクロールするとチェックボックスのチェックが外れる!?

業務でListViewにチェックボックスを使用していたのですが、
チェックしてもスクロールし戻すとチェックが外れたり付いたりする現象が発生しました。

原因・解決

以下が指定されていたのが原因でした。
Recyclingは非表示領域のコンテナを表示領域のコンテナに再利用することができます。
メモリ使用量は節約できるそうですが、上記のようにリサイクル指定すると仮想化が失敗してしまうことがよくあるそうです。
その場合はRecyclingではなくStandard(再利用はせずにコンテナの作成と破棄を繰り返す)を指定するとうまくいきます。

// 修正前
VirtualizingPanel.VirtualizationMode="Recycling"

// 修正後
VirtualizingPanel.VirtualizationMode="Standard"

参考

https://stackoverflow.com/questions/4300964/what-is-the-actual-difference-between-recycling-standard-of-virtualizationmode-p

https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/advanced/optimizing-performance-controls?view=netframeworkdesktop-4.8

https://stackoverflow.com/questions/11945563/how-listviews-recycling-mechanism-works

https://www.web-dev-qa-db-ja.com/ja/wpf/datagrid%E8%A1%8C%E4%BB%AE%E6%83%B3%E5%8C%96%E8%A1%A8%E7%A4%BA%E3%81%AE%E5%95%8F%E9%A1%8C/943670334/

https://www.codeproject.com/Articles/34405/WPF-Data-Virtualization

https://p4j4.hatenablog.com/entry/2020/12/15/035905