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 шт):
Следует использовать следующее:
<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, как в статье.
