带有列表框的 WPF 列表框 - UI 虚拟化和滚动
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1977929/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WPF ListBox with a ListBox - UI Virtualization and Scrolling
提问by Rob Buhler
My prototype displays "documents" that contain "pages" that are
represented by thumbnail images. Each document can have
any number of pages. For example, there might be
1000 documents with 5 pages each, or 5 documents with 1000 pages
each, or somewhere inbetween. Documents do not contain other documents.
In my xaml markup I have a ListBox
, whose ItemsTemplate
references an innerItemsTemplate that also has a ListBox
. I want the
2 levels of selected items so that I can perform various operations
on documents or pages (delete, merge, move to new location, etc).
The innerItemsTemplate ListBox
uses a WrapPanel
as the ItemsPanelTemplate
.
我的原型显示包含由缩略图表示的“页面”的“文档”。每个文档可以有任意页数。例如,可能有 1000 个文档,每个文档 5 页,或 5 个文档,每个文档 1000 页,或介于两者之间。文档不包含其他文档。在我的 xaml 标记中,我有一个ListBox
,它ItemsTemplate
引用了一个innerItemsTemplate,它也有一个ListBox
. 我想要 2 个级别的选定项目,以便我可以对文档或页面执行各种操作(删除、合并、移动到新位置等)。innerItemsTemplateListBox
使用 aWrapPanel
作为ItemsPanelTemplate
。
For the scenario where I have a large number of documents with a few
pages each (say, 10000 documents with 5 pages each), the scrolling
works great thanks to the UI Virtualization by the VirtualizingStackPanel
.
However, I have problems if I have a large number of pages. A document
with 1000 pages will only display about 50 at a time (whatever fits on the screen), and when I scroll down, the outer ListBox
moves to the next document, skipping the 950
pages or so that were not visible. Along with that, there is no
VirtualzingWrapPanel
so the app memory really increases.
对于我有大量文档,每个文档只有几页的场景(例如,10000 个文档,每个文档有 5 页),由于VirtualizingStackPanel
. 但是,如果我有大量页面,我会遇到问题。一个包含 1000 页的文档一次只能显示大约 50 页(无论屏幕大小如何),当我向下滚动时,外部ListBox
会移动到下一个文档,跳过大约950 页不可见的页面。除此之外,
VirtualzingWrapPanel
应用程序内存确实增加了。
I'm wondering if I am going about this the right way, especially since it is sort of difficult to explain! I would like to be able to display 10000 documents with 1000 pages each (only showing whatever fits on the screen), using UI Virtualization, and also smooth scrolling.
我想知道我是否以正确的方式解决这个问题,尤其是因为它有点难以解释!我希望能够使用 UI 虚拟化和平滑滚动显示 10000 个文档,每个文档 1000 页(仅显示适合屏幕的内容)。
How can I make sure the scrolling moves through all of the pages in document before it displays the next document, and still keep UI virtualization? The scrollbar seems to only move to the next document.
如何确保在显示下一个文档之前滚动浏览文档中的所有页面,并且仍然保持 UI 虚拟化?滚动条似乎只移动到下一个文档。
Does it seem logical to represent "documents" and "pages" -
with my current method of using a ListBox
within a ListBox
?
用我目前ListBox
在 a中使用 a 的方法来表示“文档”和“页面”似乎合乎逻辑ListBox
?
I would very much appreciate any ideas you have. Thank You.
我将非常感谢您的任何想法。谢谢你。
采纳答案by Ray Burns
The answer here is surprising:
这里的答案令人惊讶:
- If you use
ItemsControl
orListBox
you will get the behavior you are experiencing, where the control scrolls "by item" so you jump over a whole document at once, BUT - If you use
TreeView
instead, the control will scroll smoothly so you can scroll through your document and into the next one, but it will still be able to virtualize.
- 如果您使用
ItemsControl
或ListBox
您将获得您正在体验的行为,其中控件“按项目”滚动,因此您一次跳过整个文档,但是 - 如果您
TreeView
改为使用,控件将平滑滚动,因此您可以滚动浏览您的文档并进入下一个文档,但它仍然能够进行虚拟化。
I think the reason the WPF team chose this behavior is that TreeView
commonly has items that are larger than the visible area, whereas typically ListBox
es don't.
我认为 WPF 团队选择这种行为的原因是TreeView
通常有大于可见区域的项目,而通常ListBox
es 没有。
In any case, it is trivial in WPF to make a TreeView
look and act like a ListBox
or ItemsControl
by simply modifying the ItemContainerStyle
. This is very straightforward. You can roll your own or just copy over the appropriate template from the system theme file.
在任何情况下,在 WPF 中使TreeView
外观和行为类似于ListBox
或ItemsControl
通过简单地修改ItemContainerStyle
. 这是非常简单的。您可以滚动自己的模板,也可以从系统主题文件中复制相应的模板。
So you will have something like this:
所以你会有这样的事情:
<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>
Getting pixel-based scrolling and ListBox-style multiselect to work together
使基于像素的滚动和 ListBox 样式的多选协同工作
If you use this technique to get pixel-based scrolling, your outer ItemsControl which shows the documents cannot be a ListBox (because ListBox is not a subclass of TreeView or TreeViewItem). Thus you lose all of ListBox's multiselect support. As far as I can tell, there is no way to use these two features together without including some of your own code for one feature or the other.
如果您使用此技术获得基于像素的滚动,则显示文档的外部 ItemsControl 不能是 ListBox(因为 ListBox 不是 TreeView 或 TreeViewItem 的子类)。因此,您将失去所有 ListBox 的多选支持。据我所知,如果不包含您自己的某个功能的一些代码,就无法将这两个功能一起使用。
If you need both sets of functionality in the same control, you have basically several options:
如果您需要同一控件中的两组功能,您基本上有几种选择:
Implement multi-selection yourself in a subclass of TreeViewItem. Use TreeViewItem instead of TreeView for the outer control, since it allows multiple children to be selected. In the template inside ItemsContainerStyle: Add a CheckBox around the ContentPresenter, template bind the CheckBox to IsSelected, and style the CheckBox with control template to get the look you want. Then add your own mouse event handlers to handle Ctrl-Click and Shift-Click for multiselect.
Implement pixel-scrolled virtualization yourself in a subclass of VirtualizingPanel. This is relatively simple, since most of VirtualizingStackPanel's complexity is related to non-pixel scrolling and container recycling. Dan Crevier's Bloghas some useful infromation for understanding VirtualizingPanel.
在 TreeViewItem 的子类中自己实现多选。对外部控件使用 TreeViewItem 而不是 TreeView,因为它允许选择多个子项。在 ItemsContainerStyle 内的模板中:在 ContentPresenter 周围添加一个 CheckBox,模板将 CheckBox 绑定到 IsSelected,并使用控件模板设置 CheckBox 样式以获得您想要的外观。然后添加您自己的鼠标事件处理程序来处理多选的 Ctrl-Click 和 Shift-Click。
在 VirtualizingPanel 的子类中自己实现像素滚动虚拟化。这相对简单,因为 VirtualizingStackPanel 的大部分复杂性都与非像素滚动和容器回收有关。 Dan Crevier 的博客提供了一些有助于理解 VirtualizingPanel 的有用信息。
回答by Samuel Hyman
It is possible to achieve smooth scrolling VirtualizingStackPanels in WPF 4.0 without sacrificing virtualization if you're prepared to use reflection to access private functionality of the VirtualizingStackPanel. All you have to do is set the private IsPixelBased property of the VirtualizingStackPanel to true.
如果您准备使用反射来访问 VirtualizingStackPanel 的私有功能,则可以在 WPF 4.0 中实现平滑滚动 VirtualizingStackPanels 而不会牺牲虚拟化。您所要做的就是将 VirtualizingStackPanel 的私有 IsPixelBased 属性设置为 true。
Note that in .Net 4.5 there's no need for this hack as you can set VirtualizingPanel.ScrollUnit="Pixel".
请注意,在 .Net 4.5 中不需要此 hack,因为您可以设置 VirtualizingPanel.ScrollUnit="Pixel"。
To make it really easy, here's some code:
为了让它变得非常简单,这里有一些代码:
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]);
}
}
}
To use this on a ListBox, for example, you would do:
例如,要在 ListBox 上使用它,您可以执行以下操作:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel PixelBasedScrollingBehavior.IsEnabled="True">
</VirtualizingStackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
回答by Boden Garman
.NET 4.5 now has the VirtualizingPanel.ScrollUnit="ScrollUnit"
property. I just converted one of my TreeViews to a ListBox and the performance was noticeably better.
.NET 4.5 现在具有该VirtualizingPanel.ScrollUnit="ScrollUnit"
属性。我刚刚将我的一个 TreeViews 转换为 ListBox 并且性能明显更好。
More information here: http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingpanel.scrollunit(v=vs.110).aspx
更多信息:http: //msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingpanel.scrollunit(v=vs.110).aspx
回答by rmirabelle
This worked for me. Seems a couple of simple attributes will do it (.NET 4.5)
这对我有用。似乎有几个简单的属性就可以做到(.NET 4.5)
<ListBox
ItemsSource="{Binding MyItems}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel"/>
回答by kiwipom
Please allow me to preface this answer with a question: Does the user have to see each and every thumbnail within every item in the list at all times?
请允许我以一个问题作为这个答案的开头:用户是否必须始终查看列表中每个项目中的每个缩略图?
If the answer to that question is 'no', then perhaps it would be feasible to limit the number of visible pages within the inner item template (given that you have indicated the scrolling works well with, say, 5 pages) and use a separate 'selected item' template that is larger and displays all pages for that document? Billy Hollis explains how to 'pop' a selected item out in a listbox on dnrtv episode 115
如果该问题的答案是“否”,那么限制内部项目模板中可见页面的数量(假设您已经指出滚动效果很好,例如 5 个页面)并使用单独的页面可能是可行的“所选项目”模板更大并显示该文档的所有页面?比利霍利斯解释了如何在 dnrtv第 115 集的列表框中“弹出”选定的项目