Как управлять положением прокрутки ListBox в приложении MVFM WPF - PullRequest
23 голосов
/ 19 февраля 2010

У меня есть большой ListBox с включенной вертикальной прокруткой, в моем MVVM есть команды New и Edit ICommands. Я добавляю новый элемент в конец коллекции, но хочу, чтобы полоса прокрутки также автоматически позиционировалась в конец, когда я вызываю свою MVVM-AddCommand. Я также делаю элемент редактируемым (вызывая EditCommand с определенным элементом строки) из какой-то другой части приложения, чтобы мой ListBoxItem входил в режим редактирования с помощью DataTrigger, но как я выведу эту конкретную строку (ListBoxItem) в представление регулируя положение прокрутки.

Если я делаю это на стороне просмотра, я могу вызвать listBox.ScrollInToView (lstBoxItem). Но как лучше всего решить эту распространенную проблему прокрутки с точки зрения MVVM.

Ответы [ 4 ]

28 голосов
/ 19 февраля 2010

Я обычно устанавливаю IsSynchronizedWithCurrentItem="True" на ListBox. Затем я добавляю обработчик SelectionChanged и всегда отображаю выбранный элемент с кодом, подобным следующему:

    private void BringSelectionIntoView(object sender, SelectionChangedEventArgs e)
    {
        Selector selector = sender as Selector;
        if (selector is ListBox)
        {
            (selector as ListBox).ScrollIntoView(selector.SelectedItem);
        }
    }

Из моей виртуальной машины я могу получить представление коллекции по умолчанию и использовать один из методов MoveCurrent*(), чтобы убедиться, что редактируемый элемент является текущим элементом.

CollectionViewSource.GetDefaultView(_myCollection).MoveCurrentTo(thisItem);

ПРИМЕЧАНИЕ: отредактировано для использования ListBox.ScrollIntoView() для обеспечения виртуализации

7 голосов
/ 13 мая 2013

Использование этого в MVVM может быть легко выполнено посредством прикрепленного поведения, такого как:

using System.Windows.Controls;
using System.Windows.Interactivity;

namespace Jarloo.Sojurn.Behaviors
{
    public sealed class ScrollIntoViewBehavior : Behavior<ListBox>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.SelectionChanged += ScrollIntoView;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.SelectionChanged -= ScrollIntoView;
            base.OnDetaching();
        }

        private void ScrollIntoView(object o, SelectionChangedEventArgs e)
        {
            ListBox b = (ListBox) o;
            if (b == null)
                return;
            if (b.SelectedItem == null)
                return;

            ListBoxItem item = (ListBoxItem) ((ListBox) o).ItemContainerGenerator.ContainerFromItem(((ListBox) o).SelectedItem);
            if (item != null) item.BringIntoView();
        }
    }
}

Тогда в объявлении просмотра эта ссылка вверху:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

И сделай так:

<ListBox ItemsSource="{Binding MyData}" SelectedItem="{Binding MySelectedItem}">
         <i:Interaction.Behaviors>
             <behaviors:ScrollIntoViewBehavior />
         </i:Interaction.Behaviors>
</ListBox>

Теперь, когда SelectedItem изменит поведение, вызовет для вас вызов BringIntoView ().

3 голосов
/ 23 марта 2015

Это прикрепленная форма собственности принятого ответа:

using System.Windows;
using System.Windows.Controls;

namespace CommonBehaviors
{
    public static class ScrollCurrentItemIntoViewBehavior
    {
        public static readonly DependencyProperty AutoScrollToCurrentItemProperty =
            DependencyProperty.RegisterAttached("AutoScrollToCurrentItem",
                typeof(bool), typeof(ScrollCurrentItemIntoViewBehavior),
                new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));

        public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
        {
            return (bool)obj.GetValue(AutoScrollToCurrentItemProperty);
        }

        public static void OnAutoScrollToCurrentItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var listBox = obj as ListBox;
            if (listBox == null) return;

            var newValue = (bool)e.NewValue;
            if (newValue)
                listBox.SelectionChanged += listBoxSelectionChanged;
            else
                listBox.SelectionChanged -= listBoxSelectionChanged;
        }

        static void listBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var listBox = sender as ListBox;
            if (listBox == null || listBox.SelectedItem == null || listBox.Items == null) return;

            listBox.Items.MoveCurrentTo(listBox.SelectedItem);
            listBox.ScrollIntoView(listBox.SelectedItem);
        }

        public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoScrollToCurrentItemProperty, value);
        }
    }
}

Использование:

<ListBox ItemsSource="{Binding}"
          IsSynchronizedWithCurrentItem="True"
          behaviors:ScrollCurrentItemIntoViewBehavior.AutoScrollToCurrentItem="True">
1 голос
/ 12 мая 2014

Если приведенный выше код не работает для вас, попробуйте

public class ListBoxExtenders : DependencyObject
{
    public static readonly DependencyProperty AutoScrollToCurrentItemProperty = DependencyProperty.RegisterAttached("AutoScrollToCurrentItem", typeof(bool), typeof(ListBoxExtenders), new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));

    public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoScrollToSelectedItemProperty);
    }

    public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoScrollToSelectedItemProperty, value);
    }

    public static void OnAutoScrollToCurrentItemChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
    {
        var listBox = s as ListBox;
        if (listBox != null)
        {
            var listBoxItems = listBox.Items;
            if (listBoxItems != null)
            {
                var newValue = (bool)e.NewValue;

                var autoScrollToCurrentItemWorker = new EventHandler((s1, e2) => OnAutoScrollToCurrentItem(listBox, listBox.Items.CurrentPosition));

                if (newValue)
                    listBoxItems.CurrentChanged += autoScrollToCurrentItemWorker;
                else
                    listBoxItems.CurrentChanged -= autoScrollToCurrentItemWorker;
            }
        }
    }

    public static void OnAutoScrollToCurrentItem(ListBox listBox, int index)
    {
        if (listBox != null && listBox.Items != null && listBox.Items.Count > index && index >= 0)
            listBox.ScrollIntoView(listBox.Items[index]);
    }

}

Использование в XAML

<ListBox IsSynchronizedWithCurrentItem="True" extenders:ListBoxExtenders.AutoScrollToCurrentItem="True" ..../>
...