我有一个绑定在ObservableCollection
上的 WPF ListBox
。当一些内容被添加到这个集合中时,ListBox
的滚动位置会根据新增条目的大小而移动。我想保留滚动位置,使得在列表中添加内容时当前可见的条目不动,有什么方法可以实现吗?
我有一个绑定在ObservableCollection
上的 WPF ListBox
。当一些内容被添加到这个集合中时,ListBox
的滚动位置会根据新增条目的大小而移动。我想保留滚动位置,使得在列表中添加内容时当前可见的条目不动,有什么方法可以实现吗?
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添加到私有字典中,并向其添加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>
首先通过 ListBox.SelectedIndex 找到当前位置,然后使用 ListBox.ScrollIntoView(/* 当前索引 */)。即使列表中添加了新项目,您的当前位置和视图也将保持不变。
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);
}
我不确定是否有针对此的覆盖,但我怀疑没有。
如果不确定,使用手动绑定? :-} 说真的,默认的绑定行为将是 - 嗯 - 默认的,因此如果您需要特殊行为,则手动绑定是更好的选择。使用手动绑定,您可以保存滚动位置并在添加项目后重置它。
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;
}
在滚动改变时,存储垂直偏移量
private double _verticalOffset;
private void scrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var sv = (ScrollViewer)sender;
_verticalOffset = sv.VerticalOffset;
}
刷新后,滚动到先前的位置
scrollViewer?.ScrollToVerticalOffset(_verticalOffset);