У меня есть WPF ListBox
, связанный с ObservableCollection
. Когда вещи добавляются в коллекцию, позиция прокрутки ListBox
сдвигается по размеру добавленных записей. Я хотел бы иметь возможность сохранить позицию прокрутки, так что, даже когда вещи добавляются в список, элементы, которые в настоящее время отображаются, не перемещаются. Есть ли способ сделать это?
Сохранение привязанной позиции прокрутки WPF ListBox при изменении коллекции
Ответ 1
У меня была та же проблема и еще одно ограничение: пользователь не выбирает элементы, а только свитки. Поэтому метод ScrollIntoView()
бесполезен. Здесь мое решение.
Сначала я создал класс ScrollPreserver
, выведя его из DependencyObject, с прикрепленным свойством зависимостей PreserveScroll
типа bool:
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: первый элемент в видовом экране всегда будет первым элементом. Но когда первый элемент в viewport имеет другой индекс, чем 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>
Та-да! Хорошо работает:)
Ответ 2
Сначала найдите текущую позицию ListBox.SelectedIndex, а затем используйте ListBox.ScrollIntoView(/* Текущий индекс */). Даже если новые элементы будут добавлены в список, ваша текущая позиция и вид будут одинаковыми.
Ответ 3
Я не уверен, есть ли для этого переопределение, но я в этом сомневаюсь.
Если вы сомневаетесь в использовании ручной привязки?: -} Серьезно, поведение привязки по умолчанию будет - хорошо - по умолчанию, поэтому, если вам нужна специальная привязка к ручному управлению, это лучший выбор. При ручной привязке вы можете сохранить положение прокрутки и reset после добавления элементов.
Ответ 4
Создайте 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);
}
Ответ 5
Вы контролируете, когда вещи добавляются? Я имею в виду, что у вас есть одна или две определенные точки, где данные меняются? Если это так, одно простое решение (возможно, не изящное) будет иметь локальную переменную int для хранения текущего ListBox.SelectedIndex. Перед изменением данных сохраните выбранную переменную в этой переменной и после добавления данных установите для SelectedIndex списка ListBox значение этой переменной.
Ответ 6
У меня была та же самая проблема, вот что я сделал: 1. Найдите прокручиватель вашего списка, когда он ведется, и добавьте событие прокрутки.
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);