Wrap Panel 虚拟化






4.95/5 (18投票s)
WrapPanel 不支持虚拟化。但我们可以通过模拟虚拟化来提高性能。
引言
在开发 Media Assistant 期间,我遇到了几个问题,主要是关于性能的。我写了另一篇文章,标题为 用 ListBox 替换 TreeView,其中我解释了如何模拟一个列表框来使其看起来像一个树视图以支持虚拟化。我不得不这样做,因为如果大约有 20,000 个节点,树视图的性能会非常、非常慢。当我在存储库中添加缩略图视图以显示电影时,我遇到了类似的问题。我使用了一个 ListBox
,我将它的 ItemsPanel
更改为 WrapPanel
。当我使用 WrapPanel
时,ListBox
失去了它的虚拟化能力,并且需要很长时间才能在我的存储库中显示 5000 部电影。因为 WrapPanel
不支持虚拟化。在本文中,我将解释我是如何虚拟化 WrapPanel
的。
缩略图视图
带 WrapPanel 的 ListBox
我将 ListBox
的 ItemsPanel
更改为 WrapPanel
以显示缩略图视图。
<ListBox x:Name="thumbnailListBox" ItemsSource="{Binding DataSource.ResultMovies}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItem="{Binding DataSource.SelectedMovie}"
ScrollViewer.IsDeferredScrollingEnabled="True"
ItemTemplate="{StaticResource NormalThumbnailTemplate}"
ScrollViewer.ScrollChanged="HandleScrollChanged"
>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Play" Command="{Binding DataSource.PlayMovieCommand}"/>
<Separator/>
<MenuItem Header="Show in Windows Explorer"
Command="{Binding DataSource.ShowMovieInWindowsExplorerCommand}"/>
</ContextMenu>
</ListBox.ContextMenu>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
一旦我将 WrapPanel
与 ListBox
一起使用,它就会以缩略图的方式显示电影,而没有虚拟化。我找不到任何虚拟化 WrapPanel
的解决方案。所以我调查了我的代码的哪个部分耗时。然后我发现单个缩略图项目需要一些时间来渲染,因为我使用了图像,每个项目都有一些处理,而且我正在使用转换器等。
我尝试使用一个简单的边框,在 WrapPanel
中没有任何东西,它显示得非常快。所以,我尝试只对可见区域中的项目使用带有图像和其他信息的详细电影视图,而所有不在可见区域中的项目使用一个简单的边框。我不断更改视图,同时用户滚动。我订阅了 ScrollChanged
事件并使用了一个 ItemTemplate
,它在普通边框和详细电影视图之间切换。
缩略图模板
<DataTemplate x:Key="NormalThumbnailTemplate">
<Border Width="300" Height="200" BorderThickness="1" Margin="5" BorderBrush="Gray">
<ContentControl x:Name="content" Content="{Binding}" ContentTemplate="{x:Null}"/>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter TargetName="content" Value="{StaticResource NormalThumbnail}" Property="ContentTemplate"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
在电影数据中,我使用了一个属性 IsVisible
,它在 DataTrigger
中用于切换到缩略图视图。NormalThumbnail
是 DataTemplate
,它显示带有图像和其他数据的详细电影信息。所以,我不会在本文中解释那部分。
如何确定哪些项目在 ListBox 的可见区域中
private void HandleScrollChanged(object sender, ScrollChangedEventArgs e)
{
ShowVisibleItems(sender);
}
private void ShowVisibleItems(object sender)
{
var scrollViewer = (FrameworkElement) sender;
var visibleAreaEntered = false;
var visibleAreaLeft = false;
var invisibleItemDisplayed = 0;
foreach (Movie item in thumbnailListBox.Items)
{
if (item.IsVisible) continue;
var listBoxItem =
(FrameworkElement) thumbnailListBox.ItemContainerGenerator.ContainerFromItem(item);
if (visibleAreaLeft==false &&
IsFullyOrPartiallyVisible(listBoxItem, scrollViewer))
{
visibleAreaEntered = true;
}
else if (visibleAreaEntered)
{
visibleAreaLeft = true;
}
if(visibleAreaEntered)
{
if(visibleAreaLeft && ++invisibleItemDisplayed>10)
break;
var job = new Job(MakeVisible);
job.Store.Add(item);
job.Start();
}
}
}
此代码只是找到那些完全或部分可见的项目,并将它们标记为可见。我使用了一个 Job
类,它在不同的线程中标记要可见的项目。我这样做只是为了延迟触发。但在大多数情况下,这并不需要。所以,忽略那部分。
protected bool IsFullyOrPartiallyVisible(FrameworkElement child, FrameworkElement scrollViewer)
{
var childTransform = child.TransformToAncestor(scrollViewer);
var childRectangle = childTransform.TransformBounds(
new Rect(new Point(0, 0), child.RenderSize));
var ownerRectangle = new Rect(new Point(0, 0), scrollViewer.RenderSize);
return ownerRectangle.IntersectsWith(childRectangle);
}
此代码确定一个项目是否在滚动查看器的可见区域中。然后我在相同的坐标系中找到可见项目的矩形和视点矩形的交集。然后就完成了。