WPF-сброс позиции прокрутки списка при изменении ItemsSource
В настоящее время у меня есть ListBox, коллекция ItemsSource которого привязана к свойству в моей viewmodel типа IEnumerable. Когда эта ссылка preoprty изменяется, список обновляется, как и ожидалось, однако у меня есть проблема в том, что если у меня есть большая коллекция элементов и прокрутка до нижней части списка, а затем изменить ссылку на другую коллекцию, содержащую, скажем, 1 элемент, представление списка пустое и не отображается полоса прокрутки. Затем я должен прокрутить список вверх с помощью мыши колесо, пока в поле зрения не появится пункт 1.
Итак, то, что я думаю, я ищу, - это способ сброса положения прокрутки списка к началу, когда свойство ItemsSource изменяется, так что что-то всегда отображается независимо от того, насколько велика или мала коллекция.
5 ответов:
Я не могу воспроизвести вашу проблему (для меня
ListBox
прокручивается до последнего элемента в новой коллекции при измененииItemsSource
). В любом случае, чтобы прокрутитьListBox
наверх каждый раз, когда егоItemsSource
изменяет, вы можете использовать некоторый код позади. Сначала прослушайте изменения вItemsSourceProperty
, а затем прокрутитеListBox
наверх, как только его элементы будут сгенерированыОбновить
Сделал прикрепленное поведение, которое делает это вместо того, чтобы избежать кода позади. Его можно использовать как это
<ListBox ... behaviors:ScrollToTopBehavior.ScrollToTop="True"/>
ScrollToTopBehavior
public static class ScrollToTopBehavior { public static readonly DependencyProperty ScrollToTopProperty = DependencyProperty.RegisterAttached ( "ScrollToTop", typeof(bool), typeof(ScrollToTopBehavior), new UIPropertyMetadata(false, OnScrollToTopPropertyChanged) ); public static bool GetScrollToTop(DependencyObject obj) { return (bool)obj.GetValue(ScrollToTopProperty); } public static void SetScrollToTop(DependencyObject obj, bool value) { obj.SetValue(ScrollToTopProperty, value); } private static void OnScrollToTopPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e) { ItemsControl itemsControl = dpo as ItemsControl; if (itemsControl != null) { DependencyPropertyDescriptor dependencyPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ItemsControl)); if (dependencyPropertyDescriptor != null) { if ((bool)e.NewValue == true) { dependencyPropertyDescriptor.AddValueChanged(itemsControl, ItemsSourceChanged); } else { dependencyPropertyDescriptor.RemoveValueChanged(itemsControl, ItemsSourceChanged); } } } } static void ItemsSourceChanged(object sender, EventArgs e) { ItemsControl itemsControl = sender as ItemsControl; EventHandler eventHandler = null; eventHandler = new EventHandler(delegate { if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(itemsControl) as ScrollViewer; scrollViewer.ScrollToTop(); itemsControl.ItemContainerGenerator.StatusChanged -= eventHandler; } }); itemsControl.ItemContainerGenerator.StatusChanged += eventHandler; } }
И реализация GetVisualChild
private T GetVisualChild<T>(DependencyObject parent) where T : Visual { T child = default(T); int numVisuals = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < numVisuals; i++) { Visual v = (Visual)VisualTreeHelper.GetChild(parent, i); child = v as T; if (child == null) { child = GetVisualChild<T>(v); } if (child != null) { break; } } return child; }
Поздний ответ :
Простое решение-добавить обработчик событий для события
TargetUpdated
и установитьNotifyOnTargetUpdated=True
на привязкуItemsSource
:<ListBox x:Name="listBox" ItemsSource="{Binding MySource, NotifyOnTargetUpdated=True}" TargetUpdated="ListBox_TargetUpdated"/>
И в обработчике событий перейдите к верхнему пункту:
private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e) { if (listBox.Items.Count > 0) { listBox.ScrollIntoView(listBox.Items[0]); } }
Улучшенный ответа Фредрик Hedblad для работы с коллекция ObservableCollection:
public static class ItemsControlAttachedProperties { #region ScrollToTopOnItemsSourceChange Property public static readonly DependencyProperty ScrollToTopOnItemsSourceChangeProperty = DependencyProperty.RegisterAttached( "ScrollToTopOnItemsSourceChange", typeof(bool), typeof(ItemsControlAttachedProperties), new UIPropertyMetadata(false, OnScrollToTopOnItemsSourceChangePropertyChanged)); public static bool GetScrollToTopOnItemsSourceChange(DependencyObject obj) { return (bool) obj.GetValue(ScrollToTopOnItemsSourceChangeProperty); } public static void SetScrollToTopOnItemsSourceChange(DependencyObject obj, bool value) { obj.SetValue(ScrollToTopOnItemsSourceChangeProperty, value); } static void OnScrollToTopOnItemsSourceChangePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var itemsControl = obj as ItemsControl; if (itemsControl == null) { throw new Exception("ScrollToTopOnItemsSourceChange Property must be attached to an ItemsControl based control."); } DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ItemsControl)); if (descriptor != null) { if ((bool) e.NewValue) { descriptor.AddValueChanged(itemsControl, ItemsSourceChanged); } else { descriptor.RemoveValueChanged(itemsControl, ItemsSourceChanged); } } } static void ItemsSourceChanged(object sender, EventArgs e) { var itemsControl = sender as ItemsControl; DoScrollToTop(itemsControl); var collection = itemsControl.ItemsSource as INotifyCollectionChanged; if (collection != null) { collection.CollectionChanged += (o, args) => DoScrollToTop(itemsControl); } } static void DoScrollToTop(ItemsControl itemsControl) { EventHandler eventHandler = null; eventHandler = delegate { if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { var scrollViewer = GetVisualChild<ScrollViewer>(itemsControl); scrollViewer.ScrollToTop(); itemsControl.ItemContainerGenerator.StatusChanged -= eventHandler; } }; itemsControl.ItemContainerGenerator.StatusChanged += eventHandler; } static T GetVisualChild<T>(DependencyObject parent) where T : Visual { T child = default(T); int numVisuals = VisualTreeHelper.GetChildrenCount(parent); for (var i = 0; i < numVisuals; i++) { var v = (Visual) VisualTreeHelper.GetChild(parent, i); child = v as T ?? GetVisualChild<T>(v); if (child != null) { break; } } return child; } #endregion }
При форматировании элемента управления вы выбираете диапазон ячеек в качестве вариантов выбора, которые затем перечисляются в списке. Вы также выбираете ячейку в качестве ссылки на выбранные варианты, в которой будет отображаться число в зависимости от позиции выбора в списке. 1 для первого в списке,2 для второго и т. д. Код довольно прост: -
Диапазон ("A1") Select
Выбор = 1
Измените ("A1") на ячейку, которую вы связали и измените 1 на позицию в список, который вы хотите выбрать.
Ссылка на ячейку, являющаяся ссылкой, работает в обоих направлениях - если вы измените свой выбор, номер в ячейке изменится, и если вы измените номер в ячейке, выделенный выбор изменится.