WPF ListBox виснет при смене ControlTemplate

Тестовое окно с разметкой

<StackPanel>
    <Button
        Height="30"
        Click="ButtonBase_OnClick"
        Content="load"/>
    <ListBox ItemsSource="{Binding Items, Mode=OneWay}" Height="500"/>
</StackPanel>

по клику заполняем список

public IList<string> Items { get; private set; }

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    var list = new List<string>(13000);
    for (var i = 0; i < 13000; i++) list.Add($"line {i}");
    Items = list;
    OnPropertyChanged(nameof(Items));
}

все работает очень быстро и отнимает 4 мегабайта памяти.

Пытаюсь переопределить шаблон в ресурсах тестового окна.Вот тут описан дефолтный шаблон. Копирую его с себе (только часть для ListBox). Заменяю все привязки цветов на заглушки и запускаю.

И получаю взрывной рост потребления памяти, GC просто сходит с ума и в итоге даже мышь начинает тормозить и приходится убивать приложение.

Даже если оставить самый минимум.

<Window.Resources>
    <Style TargetType="ListBox">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBox">
                    <Border
                        Name="Border"
                        BorderThickness="1"
                        CornerRadius="2">
                        <ScrollViewer Margin="0" Focusable="false">
                            <StackPanel Margin="2" IsItemsHost="True" />
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

Не помогает.

Смотрел дельту снапшотов памяти - там ничего не понятно. То взрывной рост каких то List, то WeakReference сотнями тысяч

Что я делаю не так? Это не может быть баг, ведь не я первый додумался сменить шаблон контрола. Воспроизводится на net48 и netcore3.1

Так в чем же дело?

update: добавил скрин из VS

введите сюда описание изображения


Ответы (1 шт):

Автор решения: Peter Lavreniuk

Следует использовать следующее:

<ScrollViewer Margin="0" Focusable="false">
    <ItemsPresenter />
</ScrollViewer>

Мне кажеться что просто ради того что бы продемострировать возможности WPF. В статье, для того что бы не нагружать читателя, использовали StackPanel. Потому что StackPanel это универсальная вещь, которая может помочь везде и всегда. Вариативности у использования StackPanel очень много.

Следует заметить что сложные контролы требуют немного другого подхода. Так что для больших листов следует использовать ItemsPresenter.

Кстати если капнуть еще глубже, то окажется что у ListBox по дефолту стоит VirtualizingStackPanel.IsVirtualizing = True. А если еще пойти дальше - то можно найти как работает ItemsPresenter с виртуализацией - referencesource ItemsPresenter. Следовательно Вы заведомо, точнее автор статьи, использует как холдер для элементов контрол без стандартной виртулизации, поэтому на больших коллекциях и идет просадка. А в обычной реализации стиля ListBox используется ItemsPresenter.

Так что. Лучше в данном случае использовать ItemsPresenter! А бага никакого нет. Просто статья обзорная.

UPD. поигрался еще. Можно вместо StackPanel, как показано в статье, использовать VirtualizingStackPanel:

<ScrollViewer Margin="0" Focusable="false">
    <VirtualizingStackPanel IsItemsHost="True"></VirtualizingStackPanel>
</ScrollViewer>

Проблем с VirtualizingStackPanel не наблюдается!

А вот теперь давайте выключим у ListBox значение - VirtualizingStackPanel.IsVritualizing. При этом будем использовать VirtualizingStackPanel. :

    <Window.Resources>
        <Style TargetType="ListBox">
        <Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="False"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListBox">
                        <Border
                            Name="Border"
                            BorderThickness="1"
                            CornerRadius="2">
                            <ScrollViewer Margin="0" Focusable="false">
                                <VirtualizingStackPanel  IsItemsHost="True"/>
                            </ScrollViewer>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

А вот теперь просадки есть, как будто мы используем простой StackPanel, как в статье.

→ Ссылка