2009-04-29 9 views
55

Au cours de mon apprentissage de MVVM, j'ai acquis des connaissances de base sur WPF et le modèle ViewModel. J'utilise l'abstraction suivante lorsque je fournis une liste et je suis intéressé par un seul élément sélectionné.Gestion de plusieurs sélections avec MVVM

public ObservableCollection<OrderViewModel> Orders { get; private set; } 
public ICollectionView OrdersView 
{ 
    get 
    { 
     if(_ordersView == null) 
      _ordersView = CollectionViewSource.GetDefaultView(Orders); 
     return _ordersView; 
    } 
} 
private ICollectionView _ordersView; 

public OrderViewModel CurrentOrder 
{ 
    get { return OrdersView.CurrentItem as OrderViewModel; } 
    set { OrdersView.MoveCurrentTo(value); } 
} 

Je peux alors lier le OrdersView avec le soutien de tri et de filtrage à une liste WPF:

<ListView ItemsSource="{Binding Path=OrdersView}" 
      IsSynchronizedWithCurrentItem="True"> 

Cela fonctionne très bien pour les vues de sélection unique. Mais j'aimerais aussi prendre en charge plusieurs sélections dans la vue et faire en sorte que le modèle soit lié à la liste des éléments sélectionnés.

Comment lier les ListView.SelectedItems à une propriété de backer sur le ViewModel?

Répondre

92

Ajouter une propriété IsSelected à votre enfant ViewModel (OrderViewModel dans votre cas):

public bool IsSelected { get; set; } 

Bind la propriété sélectionnée sur le conteneur à ce (pour ListBox dans ce cas):

<ListBox.ItemContainerStyle> 
    <Style TargetType="{x:Type ListBoxItem}"> 
     <Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/> 
    </Style> 
</ListBox.ItemContainerStyle> 

IsSelected est mis à jour pour correspondre au champ correspondant sur le conteneur.

Vous pouvez obtenir les enfants sélectionnés dans le modèle de vue en procédant comme suit:

public IEnumerable<OrderViewModel> SelectedOrders 
{ 
    get { return Orders.Where(o => o.IsSelected); } 
} 
+29

Veuillez noter que cette solution ne fonctionne pas lorsque vous utilisez un VirtualizingStackPanel dans la ListBox (qui est la valeur par défaut). Plus d'infos sur ce post: http://stackoverflow.com/questions/1273659/virtualizingstackpanel-mvvm-multiple-selection – decasteljau

+3

Bonne prise. Merci pour la mise à jour. Pour la sélection unique, la meilleure solution est ICollectionView. Microsoft doit créer une interface ICollectionView prenant en charge la multi-sélection. –

+0

Vous venez de me sauver la vie! – jpsstavares

1

Si vous utilisez MVVM-LIGHT vous pouvez utiliser ce modèle:

http://blog.galasoft.ch/archive/2010/05/19/handling-datagrid.selecteditems-in-an-mvvm-friendly-manner.aspx

Non particulièrement élégant, mais il semble qu'il devrait être fiable au moins

+0

hmm cela ne va réellement qu'une seule façon avec cette méthode (voir> voir le modèle). pas capable de voir le modèle> voir comme décrit ici –

+2

Bien que cela puisse théoriquement répondre à la question, [il serait préférable] (http://meta.stackexchange.com/q/8259) d'inclure ici les parties essentielles de la réponse, et fournir le lien pour référence. – Kev

4

On peut essayer de créer une propriété jointe. Cela permettra d'éviter d'ajouter la propriété IsSelected pour chaque liste que vous liez. Je l'ai fait pour ListBox, mais il peut être modifié pour une utilisation dans une vue de liste.

<ListBox SelectionMode="Multiple" 
     local:ListBoxMultipleSelection.SelectedItems="{Binding SelectedItems}" > 

Plus d'info: WPF – Binding ListBox SelectedItems – Attached Property VS Style.

+1

J'aime beaucoup votre solution, mais j'ai dû modifier votre code afin qu'il ne cache pas la zone de liste dans la propriété jointe. La mise en cache de la zone de liste vous empêche d'utiliser la propriété jointe dans les autres zones de liste. – Darkhydro

11

Je peux vous assurer: SelectedItems est en effet bindable comme XAML CommandParameter

Il y a une solution simple à cette question commune; pour le faire fonctionner, vous devez suivre ALL les règles suivantes:

  1. Après Ed Ball's suggestion, sur votre commande XAML databinding, définissez l'attribut CommandParameterAVANT l'attribut Command. Ceci est un bug très long.

    enter image description here

  2. Assurez-vous de CanExecute » vos ICommand et Execute méthodes ont un paramètre de type object. De cette façon, vous pouvez éviter réduits au silence exceptions coulées qui se produisent chaque fois CommandParameter le type de la liaison de données ne correspond pas à votre type de paramètre de méthode Command:

    private bool OnDeleteSelectedItemsCanExecute(object SelectedItems) 
    { 
        // Your code goes here 
    } 
    
    private bool OnDeleteSelectedItemsExecute(object SelectedItems) 
    { 
        // Your code goes here 
    } 
    

Par exemple, vous pouvez envoyer un ListView/ListBox « s SelectedItems propriété à vos méthodes ICommand ou le ListView/ListBox lui-même. Super, n'est-ce pas?

J'espère que cela empêche quelqu'un de passer le temps énorme que j'ai fait pour comprendre comment recevoir SelectedItems comme un paramètre CanExecute.

+2

Je viens d'essayer ceci avec un DataGrid, en utilisant la propriété SelectedItems de cette manière, j'ai trouvé que CanExecute était appelé avec la valeur _previous_ si SelectedItems (c'est-à-dire qu'il ne reflétait pas l'état actuel de la sélection). Peut être différent pour d'autres contrôles, mais s'est avéré inutile dans ce cas. –

+1

A bien fonctionné pour moi (pour ListBox et DataGrid, en utilisant .Net 4.0). J'avais la commande et le paramètre CommandParameter dans l'ordre inverse de ce qui était spécifié et cela fonctionnait toujours. – BCA

+2

agréable, parfait pour listview sur 4.5.2. Ce qu'il faut ajouter, c'est que j'ai aussi utilisé DelegateCommand de Prism pour Execute et CanExecute.so: 'ICommand btnCommand = new DelegateCommand (Exécuter, CanExécuter);' – CularBytes

Questions connexes