【问题标题】:WPF ScrollViewer with ListBoxWPF ScrollViewer 与 ListBox
【发布时间】:2011-04-17 00:18:43
【问题描述】:

需要你的帮助。我有一个显示 ScrollViewer 的 ListBox(带有虚拟化)。 我的 ListBox 项是可展开的,在展开时它们的高度可能会超出可见的滚动区域。

我遇到的问题是,当列表框项目超出可见滚动区域时 - 滚动跳转到下一个列表框项目,而不是简单地滚动视图。

检查此代码:

    <ListBox Grid.Row="1" Grid.Column="0" DataContext="{Binding SpecPackageSpecGroupListViewModel}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"
                     ItemContainerStyle="{StaticResource SpecPackageSpecGroupListBoxStyle}" ScrollViewer.IsDeferredScrollingEnabled="True" 
                     ItemsSource="{Binding SortedChildren}" ScrollViewer.CanContentScroll="True"
                     Background="Transparent"
                     BorderThickness="0" SelectionMode="Extended"
                     Margin="5,5,5,5">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Controls:SpecPackageSpecGroupControl/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

当然,我不能用另一个滚动条包裹我的 ListBox,因为它会关闭可视化(这对我来说非常无能)。

如果我将 CanContentScroll 设置为 False,一切都按预期工作 - 但虚拟化停止工作。

求助!!!

吉利

【问题讨论】:

    标签: wpf scroll


    【解决方案1】:

    好的,所以就在我即将放弃并以某种方式学习如何忍受这个错误之前,我碰到了一个帖子(我现在似乎找不到),它表明 TreeView 确实支持基于像素的滚动( AKA 物理滚动)而不关闭可视化。

    所以我尝试了这个,确实 - 它有效!确保验证虚拟化是否有效,测试了大约 1000 个项目,还在我的控件构造函数上设置了一个断点,并确保在我的视图滚动时调用它。

    使用 TreeView 而不是 ListBox 的唯一缺点是 TreeView 似乎不支持多项选择(我需要) - 但实现这一点比实现 ListBox 的智能滚动要容易得多。

    我为 TreeViewItem 创建了一个样式,它使 TreeViewItem 的外观和行为就像 ListBoxItem 一样,这实际上不是强制性的 - 但我更喜欢这样(除了基本样式有拉伸问题,我必须通过样式修复)。基本上我删除了 ItemsPresenter 并只保留了 ContentPresenter,因为我的数据不是分层的:

        <Style x:Key="MyTreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="SnapsToDevicePixels" Value="true"/>
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="VerticalContentAlignment" Value="Stretch"/>
            <Setter Property="OverridesDefaultStyle" Value="true"/>    
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TreeViewItem}">
                        <Border Name="myBorder" 
                            SnapsToDevicePixels="true" 
                            CornerRadius="0,0,0,0" 
                            VerticalAlignment="Stretch" 
                            HorizontalAlignment="Stretch"
                            BorderThickness="0"
                            BorderBrush="Transparent"
                            Height="Auto"
                            Margin="1,1,1,3" 
                            Background="Transparent">
                            <ContentPresenter Grid.Column="1" x:Name="PART_Header" HorizontalAlignment="Stretch" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" ContentSource="Header"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    

    现在 - 我唯一要做的就是实现多选树视图。 实现这种行为可能有不同的方法,我采用了 ViewModel 方法。

    从 TreeView 派生,我创建了一个新的 MultiSelectionTreeView:

    public class MultiSelectionTreeView : TreeView
    {
        private static bool CtrlPressed
        {
            get
            {
                return Keyboard.IsKeyDown(Key.LeftCtrl);
            }
        }
    
        protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
        {
            base.OnSelectedItemChanged(e);
    
            var previouseItemViewModel = e.OldValue as IMultiSelectionTreeViewItemViewModel;
            if (previouseItemViewModel != null)
            {
                if (!CtrlPressed)
                    previouseItemViewModel.IsSelected = false;
            }                        
    
            var newItemViewModel = e.NewValue as IMultiSelectionTreeViewItemViewModel;
            if (newItemViewModel != null)
            {
                if (!CtrlPressed)
                    newItemViewModel.ClearSelectedSiblings();
                newItemViewModel.IsSelected = true;
            }                
        }
    }
    

    其中IMultiSelectionTreeViewItemViewModel如下:

    public interface IMultiSelectionTreeViewItemViewModel
    {
        bool IsSelected { get; set; }
        void ClearSelectedSiblings();
    }
    

    当然 - 现在我有责任处理所选项目的呈现方式 - 在我的情况下,它是因为我的树视图项目有自己的 DataTemplate 来指示其选择的。 如果这不是您的情况并且您需要它,只需扩展您的树视图项数据模板以根据其视图模型 IsSelected 属性指示其选择状态。

    希望有一天这会对某人有所帮助:-) 玩得开心!

    吉利

    【讨论】:

      【解决方案2】:

      这个问题还在搜索引擎中出现,所以我会在 2 年后回答。

      WPF 4.5 现在支持基于像素的虚拟化面板。

      如果您可以定位 4.5,那么只需将其添加到您的 ListBox:

      <Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel"/>
      

      有关 .NET 4.5 中的新功能(包括 WPF 4.5 中的新内容),请参阅 http://msdn.microsoft.com/en-us/library/ms171868.aspx

      【讨论】:

        【解决方案3】:

        看看here (Bea Stollnitz)here(Dan Crevier);基本上,您需要实现自己的容器,该容器同时支持虚拟化和基于像素的滚动。您还可以查看this similar SO question 了解更多详细信息或可能的替代方案。从 VirtualizingStackPanel 派生并修改滚动行为似乎是最好的选择。

        【讨论】:

        • 花这么多时间把我的系统置于如此危险的境地对我来说太荒谬了。我们正在开发系统而不是控制。简单地将树视图调整为此要容易得多。如果没有简单的解决方案,我从不说永远 - 作为开发人员,我们需要做任何需要做的事情,但由于这里有更好的选择 - 我不明白为什么要冒险。
        • 顺便说一句:这是一个错误。微软应该注意这一点。基于像素的滚动和虚拟化之间没有任何冲突。对此的证明是它在 TreeView 中运行良好,由于异端结构,他们没有选择。那里有人偷懒... :-)
        • 实际上 TreeView 根本没有虚拟化,这是另一个令人头疼的问题。正确实施它是一种适当的痛苦。当然,这很困难,因为每个父项的项都由另一个 ItemsControl 表示,并且使每个嵌入式 ItemsControl 都知道最外层容器的大小和属性以便虚拟化可以正常工作是很难做到的。这就是 TreeView 的性能对于少量项目之外的任何东西来说真的很糟糕的原因。
        猜你喜欢
        • 1970-01-01
        • 2011-07-13
        • 2010-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多