在集合更改时保留绑定的WPF ListBox的滚动位置

时间:2009-05-13 22:33:09

标签: wpf listbox

我有一个绑定到ListBox的WPF ObservableCollection。当事物被添加到集合中时,ListBox滚动位置会根据添加的条目的大小而变化。我希望能够保留滚动位置,这样即使将事物添加到列表中,当前视图中的项目也不会移动。有没有办法实现这个目标?

6 个答案:

答案 0 :(得分:3)

我遇到了同样的问题和另外一个限制:用户不选择项目,而只是滚动。这就是ScrollIntoView()方法无用的原因。这是我的解决方案。

首先,我创建了一个类ScrollPreserver,它从DependencyObject派生,带有bool类型的附加依赖属性PreserveScroll

public class ScrollPreserver : DependencyObject
{
    public static readonly DependencyProperty PreserveScrollProperty =
        DependencyProperty.RegisterAttached("PreserveScroll", 
            typeof(bool),
            typeof(ScrollPreserver), 
            new PropertyMetadata(new PropertyChangedCallback(OnScrollGroupChanged)));

    public static bool GetPreserveScroll(DependencyObject invoker)
    {
        return (bool)invoker.GetValue(PreserveScrollProperty);
    }

    public static void SetPreserveScroll(DependencyObject invoker, bool value)
    {
        invoker.SetValue(PreserveScrollProperty, value);
    }

    ...
}

属性更改回调假定属性由ScrollViewer设置。回调方法将此ScrollViewer添加到私有Dictionary并向其添加ScrollChanged事件处理程序:

private static Dictionary<ScrollViewer, bool> scrollViewers_States = 
    new Dictionary<ScrollViewer, bool>();

private static void OnScrollGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ScrollViewer scrollViewer = d as ScrollViewer;
    if (scrollViewer != null && (bool)e.NewValue == true)
    {
        if (!scrollViewers_States.ContainsKey(scrollViewer))
        {
            scrollViewer.ScrollChanged += new    ScrollChangedEventHandler(scrollViewer_ScrollChanged);
            scrollViewers_States.Add(scrollViewer, false);
        }
    }
}

此字典将保存对使用此类的应用程序中的所有ScrollViewers的引用。在我的情况下,项目将添加到集合的开头。问题是视口位置不会改变。当位置为0时,它是正常的:视口中的第一个元素将始终是第一个项目。但是当视口中的第一个元素具有另一个索引而不是0并添加了新项时 - 该元素的索引会增加,因此它会向下滚动。因此bool值指示ScrollViewer的垂直偏移量是否为0.如果为0则不需要执行任何操作,如果不是0,则必须保留其相对于视口中项目的位置:

static void scrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
    if (scrollViewers_States[sender as ScrollViewer])
        (sender as ScrollViewer).ScrollToVerticalOffset(e.VerticalOffset + e.ExtentHeightChange);

    scrollViewers_States[sender as ScrollViewer] = e.VerticalOffset != 0;
}

此处使用ExtentHeightChange来指示添加的项目数量。在我的情况下,项目仅在集合的开头添加,所以我需要做的就是通过此值增加ScrollViewer的VerticalOffset。 最后一件事:用法。以下是ListBox的示例:

<Listbox ...> 
    <ListBox.Resourses>
        <Style TargetType="ScrollViewer">
            <Setter Property="local:ScrollPreserver.PreserveScroll" Value="True" />
        </Style>
    </ListBox.Resourses>
</ListBox>

钽哒!效果很好:))

答案 1 :(得分:1)

首先通过ListBox.SelectedIndex找到当前位置,然后使用ListBox.ScrollIntoView(/ * Current Index * /)。即使新项目已添加到列表中,您当前的位置和视图也会相同。

答案 2 :(得分:0)

我不确定是否有覆盖,但我对此表示怀疑。

如有疑问,请使用手动装订? : - }严重的是,默认的绑定行为是 - well - 默认,所以如果你需要特殊的行为,手动绑定是一个更好的选择。使用手动装订,您可以保存滚动位置,并在添加项目后重置它。

答案 3 :(得分:0)

创建一个CollectionView。 试试这个:

    private ObservableCollection<int> m_Values;
    private CollectionView m_View;

    private void Bind()
    {
        m_Values = new ObservableCollection<int>();
        m_View = new CollectionView(m_Values);
        MyListBox.ItemsSource = m_View;
    }

    private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        Debug.WriteLine(m_View.CurrentItem);
        Debug.WriteLine(m_View.CurrentPosition);
    }

答案 4 :(得分:0)

你能控制什么时候添加东西吗?我的意思是你有一个或两个定义点数据变化?如果是这样,一个简单的解决方案(可能不优雅)将有一个本地int变量来保存当前的ListBox.SelectedIndex。在数据更改之前,将selectedIndex保存到此变量,并在添加数据后,将listBox的SelectedIndex设置为此变量的值。

答案 5 :(得分:0)

我遇到了同样的问题,这是我所做的: 1.在列表框处于引导状态时找到它的滚动查看器,并添加一个scrollchanged事件。

var scrollViewer = FindScrollViewer(ListBoxOrders);
if (scrollViewer != null)
{
    scrollViewer.ScrollChanged += scrollViewer_ScrollChanged;
}

以下是查找滚动查看器的功能:

    private ScrollViewer FindScrollViewer(DependencyObject d)
{
    if (d is ScrollViewer)
        return d as ScrollViewer;

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(d); i++)
    {
        var sw = FindScrollViewer(VisualTreeHelper.GetChild(d, i));
        if (sw != null) return sw;
    }
    return null;
}
  1. 更改滚动时,存储垂直偏移

    private double _verticalOffset; private void scrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { var sv = (ScrollViewer)sender; _verticalOffset = sv.VerticalOffset; }

  2. 刷新后,滚动到上一个位置

    scrollViewer?.ScrollToVerticalOffset(_verticalOffset);