Ответ 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>
Та-да! Хорошо работает:)