Синхронизация множественного выбора ListBox с MVVM - PullRequest
17 голосов
/ 11 ноября 2011

У меня есть два представления некоторых данных: представление списка (сейчас ListBox, но я собирался переключиться на ListView) и причудливое графическое представление на карте.В любом представлении пользователь может щелкнуть объект, и он будет выбран в обоих представлениях.Multiselect также возможен, поэтому каждый экземпляр ViewModel имеет свое собственное свойство IsSelected.

В настоящее время я связываю ListBoxItem.IsSelected с ViewModel.IsSelected, но это работает только в том случае, если ListBox НЕ имеет значениявиртуализация ( см. здесь ).К сожалению, отключение виртуализации снижает производительность, и мое приложение становится слишком медленным.

Поэтому я должен снова включить виртуализацию.Чтобы сохранить свойство ViewModel.IsSelected объектов вне экрана, я заметил, что ListBox и ListView имеют событие SelectionChanged, которое я могу (предположительно) использовать для передачи состояния выбора из ListBox/ListView вViewModel.

У меня вопрос, как распространять состояние выбора в обратном направлении?Свойство SelectedItems ListBox/ListView доступно только для чтения!Предположим, что пользователь щелкает элемент в графическом представлении, но он находится вне экрана в списке.Если я просто установлю ViewModel.IsSelected, тогда ListBox/ListView не будет знать о новом выделении, и, как следствие, ему не удастся отменить выбор этого элемента, если пользователь щелкнет другой элемент в списке.Я мог бы позвонить ListBox.ScrollIntoView из ViewModel, но есть пара проблем:

  • В моем пользовательском интерфейсе фактически возможно выбрать два элемента одним щелчком мыши, если они находятся в одном месте графическиХотя они могут быть расположены в совершенно разных местах в ListBox/ListView.
  • Это нарушает изоляцию ViewModel (моя ViewModel совершенно не знает о WPF, и я бы хотел оставить это так.)

Итак, мои дорогие эксперты WPF, есть какие-нибудь мысли?

РЕДАКТИРОВАТЬ: я закончил тем, что переключился на управление Infragistics и использовал уродливое и довольно медленное решение.Дело в том, что мне больше не нужен ответ.

1 Ответ

25 голосов
/ 11 ноября 2011

Вы можете создать Поведение , которое синхронизирует ListBox.SelectedItems с коллекцией в вашей ViewModel:

public class MultiSelectionBehavior : Behavior<ListBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        if (SelectedItems != null)
        {
            AssociatedObject.SelectedItems.Clear();
            foreach (var item in SelectedItems)
            {
                AssociatedObject.SelectedItems.Add(item);
            }
        }
    }

    public IList SelectedItems
    {
        get { return (IList)GetValue(SelectedItemsProperty); }
        set { SetValue(SelectedItemsProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectionBehavior), new UIPropertyMetadata(null, SelectedItemsChanged));

    private static void SelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var behavior = o as MultiSelectionBehavior;
        if (behavior == null)
            return;

        var oldValue = e.OldValue as INotifyCollectionChanged;
        var newValue = e.NewValue as INotifyCollectionChanged;

        if (oldValue != null)
        {
            oldValue.CollectionChanged -= behavior.SourceCollectionChanged;
            behavior.AssociatedObject.SelectionChanged -= behavior.ListBoxSelectionChanged;
        }
        if (newValue != null)
        {
            behavior.AssociatedObject.SelectedItems.Clear();
            foreach (var item in (IEnumerable)newValue)
            {
                behavior.AssociatedObject.SelectedItems.Add(item);
            }

            behavior.AssociatedObject.SelectionChanged += behavior.ListBoxSelectionChanged;
            newValue.CollectionChanged += behavior.SourceCollectionChanged;
        }
    }

    private bool _isUpdatingTarget;
    private bool _isUpdatingSource;

    void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (_isUpdatingSource)
            return;

        try
        {
            _isUpdatingTarget = true;

            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    AssociatedObject.SelectedItems.Remove(item);
                }
            }

            if (e.NewItems != null)
            {
                foreach (var item in e.NewItems)
                {
                    AssociatedObject.SelectedItems.Add(item);
                }
            }

            if (e.Action == NotifyCollectionChangedAction.Reset)
            {
                AssociatedObject.SelectedItems.Clear();
            }
        }
        finally
        {
            _isUpdatingTarget = false;
        }
    }

    private void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (_isUpdatingTarget)
            return;

        var selectedItems = this.SelectedItems;
        if (selectedItems == null)
            return;

        try
        {
            _isUpdatingSource = true;

            foreach (var item in e.RemovedItems)
            {
                selectedItems.Remove(item);
            }

            foreach (var item in e.AddedItems)
            {
                selectedItems.Add(item);
            }
        }
        finally
        {
            _isUpdatingSource = false;
        }
    }

}

Это поведение можно использовать, как показано ниже:

        <ListBox ItemsSource="{Binding Items}"
                 DisplayMemberPath="Name"
                 SelectionMode="Extended">
            <i:Interaction.Behaviors>
                <local:MultiSelectionBehavior SelectedItems="{Binding SelectedItems}" />
            </i:Interaction.Behaviors>
        </ListBox>

(обратите внимание, что коллекция SelectedItems в вашей ViewModel должна быть инициализирована; поведение не будет устанавливать ее, она будет только изменять ее содержимое)

...