WPF中的ListBox滚动条视图

4
需要您的帮助。我有一个ListBox(具有虚拟化),它显示一个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,一切都按预期工作 - 但虚拟化停止工作。
救命啊!
Gili
3个回答

3

好的,在我准备放弃并学会如何处理这个错误之前,我遇到了一篇文章(现在似乎无法找到),它建议TreeView支持基于像素的滚动(也称为物理滚动),而不需要关闭可视化。

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

与ListBox相比,使用TreeView唯一的缺点是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,这个DataTemplate中指示了它的选择状态。

如果这不是你的情况,并且你需要它,请简单地扩展你的树形视图项数据模板来指示其选择状态,根据其视图模型的IsSelected属性。

希望这将在某一天帮助到别人 :-) 祝愉快!

Gili


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


1

我花费这么多时间并将我的系统置于如此风险中是荒谬的。我们正在开发系统而不是控件。将树形视图简单地调整到这个状态要容易得多。我从不说永远,如果没有简单的解决方案 - 作为开发人员,我们需要做任何必要的事情,但既然有更好的选择 - 我不明白为什么要冒险。 - Gilad
1
顺便说一下:这是一个错误。微软应该处理这个问题。像素滚动和虚拟化之间根本没有冲突。证明就在于TreeView中它可以很好地工作,因为由于其异端结构,他们别无选择。有人在那里偷懒了... :-) - Gilad
实际上,TreeView根本没有虚拟化,这是另一个头疼的问题。正确地实现它是一种真正的痛苦。当然,这只是因为每个父项都由另一个ItemsControl表示,使每个嵌套的ItemsControl意识到最外层容器的大小和属性,以便虚拟化能够正常工作是很难做到的。这就是为什么TreeView的性能在超过少量项目时真的很差的原因。 - Alex Paven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接