Synchronisation multi-select ListBox avec MVVM

j'ai deux vues de certaines données: une vue de liste (a ListBox maintenant, mais j'ai eu l'intention de passer à ListView) et une représentation graphique sur une carte. Dans la vue de l'utilisateur peut cliquer sur un objet et il sera sélectionné dans les deux vues. Multi-Select est également possible, donc chaque ViewModel l'instance a sa propre IsSelected propriété.

actuellement je lie ListBoxItem.IsSelectedViewModel.IsSelected, mais cela ne fonctionne correctement que si le ListBox n'est pas virtualisante (voir ici). Malheureusement, désactiver la virtualisation nuit aux performances et mon application est devenue trop lente.

donc je dois à nouveau activer la virtualisation. Afin de maintenir l' ViewModel.IsSelected propriété des objets hors écran, j'ai remarqué que ListBox et ListViewSelectionChanged événement que je peux (sans doute) utiliser pour propager l'état de la sélection de l' ListBox/ListViewViewModel.

ma question Est, comment propager l'état de sélection dans la direction opposée? SelectedItems propriété de ListBox/ListView est en lecture seule! Supposons que l'utilisateur clique sur un élément dans la représentation graphique, mais il est hors de l'écran.w.r.t. la liste. Si je viens de mettre ViewModel.IsSelected puis ListBox/ListView ne sera pas au courant de la nouvelle sélection, et par conséquent, il échouera à désélectionner si l'utilisateur clique sur un élément différent dans la liste. Je pouvais l'appeler ListBox.ScrollIntoViewViewModel, mais il ya un couple de problèmes:

  • dans mon UI il est en fait possible de sélectionner deux éléments avec un cliquez s'ils se trouvent au même endroit graphiquement, bien qu'ils puissent se trouver à des endroits complètement différents dans le ListBox/ListView.
  • il brise l'isolement de ViewModel (mon ViewModel n'est absolument pas au courant de WPF et j'aimerais le garder ainsi.)

Alors, mes chers experts WPF, des idées?

EDIT: j'ai fini par passer à un contrôle Infragistique et en utilisant une solution Moche et plutôt lente. Le point est, je n'ai plus besoin d'une réponse.

16
demandé sur Community 2011-11-11 05:20:30

1 réponses

Vous pouvez créer un Comportement synchronisation

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;
        }
    }

}

ce comportement peut être utilisé comme suit:

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

(notez que le SelectedItems collection dans votre Dernier doit être initialisé; le comportement ne sera pas réglé, il va seulement changer son contenu)

22
répondu Thomas Levesque 2016-11-30 12:44:38