带ListBox的WPF列表框 – UI虚拟化和滚动

我的原型显示包含由缩略图表示的“页面”的“文档”。 每个文档可以有任意数量的页面。 例如,可能有1000个文档,每个5个页面,或者5个文档每个1000个页面,或者中间的某个位置。 文件不包含其他文件。 在我的xaml标记中,我有一个ListBox ,其ItemsTemplate引用一个innerItemsTemplate也有一个ListBox 。 我想要2级选定的项目,以便我可以在文档或页面上执行各种操作(删除,合并,移动到新位置等)。 innerItemsTemplate ListBox使用一个WrapPanel作为ItemsPanelTemplate。

对于每个页面数量较多(比如10000个文档,每页5个页面)的大量文档,滚动效果非常好,这得益于VirtualizingStackPanel的UI虚拟VirtualizingStackPanel 。 但是,如果我有大量的页面,则会出现问题。 一个1000页的文档一次只能显示大约50个(无论在屏幕上),而当我向下滚动时,外部ListBox移动到下一个文档,跳过950页左右,这是不可见的。 随着这一点,没有VirtualzingWrapPanel所以应用程序内存真的增加。

我想知道我是否正在以正确的方式进行,特别是因为这很难解释! 我希望能够显示10000个文件,每个1000页(只显示任何适合的屏幕),使用UI虚拟化,也顺利滚动。

我怎样才能确保滚动浏览文档中的所有页面,然后显示下一个文档,并保持UI虚拟化? 滚动条似乎只移动到下一个文档。

代表“文件”和“页面”似乎是合乎逻辑的 – 用我目前在ListBox使用ListBox ListBox

我非常感谢你有任何想法。 谢谢。

这里的答案是令人惊讶的:

  • 如果你使用ItemsControl或者ListBox你会得到你正在经历的行为,在这个控件中“逐项”滚动,所以你一次跳过整个文档,但是
  • 如果您使用TreeView ,则控件将平滑滚动,以便您可以滚动文档并进入下一个文档,但它仍然可以虚拟化。

我认为WPF团队select这种行为的原因是TreeView通常具有大于可见区域的项目,而通常ListBox es不包含这些项目。

在任何情况下,在WPF中通过简单地修改ItemContainerStyle来使TreeView看起来像ListBoxItemsControl简单。 这非常简单。 您可以自行滚动,也可以从系统主题文件中复制适当的模板。

所以你会有这样的东西:

 <TreeView ItemsSource="{Binding documents}"> <TreeView.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel /> </ItemsPanelTemplate> </TreeView.ItemsPanel> <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TreeViewItem}"> <ContentPresenter /> <!-- put your desired container style here with a ContentPresenter inside --> </ControlTemplate> </Setter.Value> </Setter> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <DataTemplate TargetType="{x:Type my:Document}"> <Border BorderThickness="2"> <!-- your document frame will be more complicated than this --> <ItemsControl ItemsSource="{Binding pages}"> ... </ItemsControl> </Border> </DataTemplate> </TreeView.ItemTemplate> </TreeView> 

获得基于像素的滚动和ListBox风格的多重select一起工作

如果您使用此技术来获取基于像素的滚动,则显示文档的外部ItemsControl不能是ListBox(因为ListBox不是TreeView或TreeViewItem的子类)。 因此,你失去了所有ListBox的多选支持。 据我所知,没有包括一些你自己的代码或一个function,没有办法一起使用这两个function。

如果在同一个控件中同时需要两组function,那么基本上有几个选项:

  1. 在TreeViewItem的子类中自己实现多选。 使用TreeViewItem而不是TreeView作为外部控件,因为它允许select多个子项。 在ItemsContainerStyle中的模板中:在ContentPresenter周围添加一个CheckBox,模板将CheckBox绑定到IsSelected,并使用控件模板对CheckBox进行样式化,以获得所需的外观。 然后添加您自己的鼠标事件处理程序来处理多选的Ctrl-Click和Shift-Click。

  2. 在VirtualizingPanel的子类中自己实现像素滚动的虚拟化。 这是相对简单的,因为VirtualizingStackPanel的大部分复杂性都与非像素滚动和容器回收有关。 Dan Crevier的博客对理解VirtualizingPanel有一些有用的信息。

如果您准备使用reflection访问VirtualizationStackPanel的专用function,则可以在不牺牲虚拟化的情况下实现在WPF 4.0中平滑滚动VirtualizingStackPanel。 您只需将VirtualizationStackPanel的私有IsPixelBased属性设置为true即可。

请注意,在.Net 4.5中,您可以设置VirtualizingPanel.ScrollUnit =“Pixel”,因此无需进行此类攻击。

为了使它很容易,这里有一些代码:

 public static class PixelBasedScrollingBehavior { public static bool GetIsEnabled(DependencyObject obj) { return (bool)obj.GetValue(IsEnabledProperty); } public static void SetIsEnabled(DependencyObject obj, bool value) { obj.SetValue(IsEnabledProperty, value); } public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(PixelBasedScrollingBehavior), new UIPropertyMetadata(false, HandleIsEnabledChanged)); private static void HandleIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var vsp = d as VirtualizingStackPanel; if (vsp == null) { return; } var property = typeof(VirtualizingStackPanel).GetProperty("IsPixelBased", BindingFlags.NonPublic | BindingFlags.Instance); if (property == null) { throw new InvalidOperationException("Pixel-based scrolling behaviour hack no longer works!"); } if ((bool)e.NewValue == true) { property.SetValue(vsp, true, new object[0]); } else { property.SetValue(vsp, false, new object[0]); } } } 

例如,要在ListBox上使用它,你可以这样做:

 <ListBox> <ListBox.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel PixelBasedScrollingBehavior.IsEnabled="True"> </VirtualizingStackPanel> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox> 

.NET 4.5现在具有VirtualizingPanel.ScrollUnit="ScrollUnit"属性。 我只是将我的一个TreeView转换成ListBox,性能明显更好。

更多信息在这里: http : //msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingpanel.scrollunit(v=vs.110).aspx

这对我有效。 似乎有几个简单的属性会做到这一点(.NET 4.5)

 <ListBox ItemsSource="{Binding MyItems}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.ScrollUnit="Pixel"/> 

请允许我用一个问题作为序言:用户是否必须始终查看列表中每个项目中的每个缩略图?

如果对这个问题的答案是“否”,那么也许可以限制内部项目模板中的可见页面的数量(假设你已经指出滚动可以很好地用5页)并且使用单独的“选定的项目”模板更大,并显示该文档的所有页面? 比利·霍利斯(Billy Hollis)解释了如何在dnrtv 第115集的列表框中“popup”选定的项目