带有ListBox的WPF ListBox - UI虚拟化和滚动

Rob*_*ler 26 wpf virtualization user-interface scroll listbox

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

对于我有大量文档的情况,每个文档都有几个页面(比如10000个文档,每个页面有5个页面),滚动工作非常好,这要归功于UI虚拟化VirtualizingStackPanel.但是,如果我有大量的页面,我会遇到问题.具有1000页的文档一次仅显示约50个(无论何种适合屏幕),当我向下滚动时,外部ListBox移动到下一个文档,跳过不可见的950页左右.除此之外,VirtualzingWrapPanel应用程序内存确实没有 增加.

我想知道我是否以正确的方式解决这个问题,特别是因为它有点难以解释!我希望能够显示10000个文档,每个文档1000页(仅显示适合屏幕的任何内容),使用UI虚拟化,以及平滑滚动.

如何在显示下一个文档之前确保滚动在文档中的所有页面中移动,并且仍然保持UI虚拟化?滚动条似乎只移动到下一个文档.

表示"文档"和"页面"似乎合乎逻辑 - 使用我当前ListBox在a 中使用a的方法ListBox

我非常感谢你的任何想法.谢谢.

Sam*_*ack 44

如果您准备使用反射来访问VirtualizingStackPanel的私有功能,则可以在WPF 4.0中实现平滑滚动VirtualizingStackPanel而不牺牲虚拟化.您所要做的就是将VirtualizingStackPanel的私有IsPixelBased属性设置为true.

请注意,在.Net 4.5中,不需要这个hack,因为您可以设置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]);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

例如,要在ListBox上使用它,您可以:

<ListBox>
   <ListBox.ItemsPanel>
      <ItemsPanelTemplate>
         <VirtualizingStackPanel PixelBasedScrollingBehavior.IsEnabled="True">
          </VirtualizingStackPanel>
       </ItemsPanelTemplate>
   </ListBox.ItemsPanel>
</ListBox>
Run Code Online (Sandbox Code Playgroud)

  • 这真的很有帮助,+ 1.对于使用.NET 4.5的未来访问者,您需要在`ListBox`本身上设置`VirtualizingPanel.ScrollUnit ="Pixel"`,而不是在保存内容的`VirtualizingStackPanel`上. (20认同)

Ray*_*rns 25

这里的答案令人惊讶:

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

我认为WPF团队选择此行为的原因TreeView通常是项目大于可见区域,而通常情况下ListBox不会.

在任何情况下,在WPF中TreeView看起来像一个ListBoxItemsControl简单地修改它是微不足道的ItemContainerStyle.这非常简单.您可以自己滚动,也可以只从系统主题文件中复制相应的模板.

所以你会有这样的事情:

<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>
Run Code Online (Sandbox Code Playgroud)

使基于像素的滚动和ListBox样式的多选项一起工作

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

如果在同一个控件中需要两组功能,基本上有几个选项:

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

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


Bod*_*man 16

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

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


rmi*_*lle 7

这对我有用.似乎有几个简单的属性可以做到(.NET 4.5)

<ListBox            
    ItemsSource="{Binding MyItems}"
    VirtualizingStackPanel.IsVirtualizing="True"
    VirtualizingStackPanel.ScrollUnit="Pixel"/>
Run Code Online (Sandbox Code Playgroud)

  • “VirtualizingStackPanel”派生自“VirtualizingPanel”。`IsVirtualizingProperty` 和 `ScrollUnitProperty` 在基类上定义。您可以通过子类型引用它们,但从直接声明属性的类型中引用它们可能更清楚。 (2认同)