带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
看起来像ListBox
或ItemsControl
简单。 这非常简单。 您可以自行滚动,也可以从系统主题文件中复制适当的模板。
所以你会有这样的东西:
<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,那么基本上有几个选项:
-
在TreeViewItem的子类中自己实现多选。 使用TreeViewItem而不是TreeView作为外部控件,因为它允许select多个子项。 在ItemsContainerStyle中的模板中:在ContentPresenter周围添加一个CheckBox,模板将CheckBox绑定到IsSelected,并使用控件模板对CheckBox进行样式化,以获得所需的外观。 然后添加您自己的鼠标事件处理程序来处理多选的Ctrl-Click和Shift-Click。
-
在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”选定的项目