web-dev-qa-db-fra.com

Sélectionner plusieurs éléments d'un DataGrid dans un projet MVVM WPF

Comment sélectionner plusieurs éléments d'une DataGrid dans un projet MVVM WPF?

55
MegaMind

Vous pouvez simplement ajouter une propriété de dépendance custom pour ce faire:

public class CustomDataGrid : DataGrid
{
    public CustomDataGrid ()
    {
        this.SelectionChanged += CustomDataGrid_SelectionChanged;
    }

    void CustomDataGrid_SelectionChanged (object sender, SelectionChangedEventArgs e)
    {
        this.SelectedItemsList = this.SelectedItems;
    }
    #region SelectedItemsList

    public IList SelectedItemsList
    {
        get { return (IList)GetValue (SelectedItemsListProperty); }
        set { SetValue (SelectedItemsListProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemsListProperty =
            DependencyProperty.Register ("SelectedItemsList", typeof (IList), typeof (CustomDataGrid), new PropertyMetadata (null));

    #endregion
}

Vous pouvez maintenant utiliser cette dataGrid dans le code XAML:

<Window x:Class="DataGridTesting.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"
    xmlns:local="clr-namespace:DataGridTesting.CustomDatagrid"
    Title="MainWindow"
    Height="350"
    Width="525">
  <DockPanel>
    <local:CustomDataGrid ItemsSource="{Binding Model}"
        SelectionMode="Extended"
        AlternatingRowBackground="Aquamarine"
        SelectionUnit="FullRow"
        IsReadOnly="True"
        SnapsToDevicePixels="True"
        SelectedItemsList="{Binding TestSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
  </DockPanel>
</Window>

Ma ViewModel:

public class MyViewModel : INotifyPropertyChanged
{
    private static object _lock = new object ();
    private List<MyModel> _myModel;

    public IEnumerable<MyModel> Model { get { return _myModel; } }

    private IList _selectedModels = new ArrayList ();

    public IList TestSelected
    {
        get { return _selectedModels; }
        set
        {
            _selectedModels = value;
            RaisePropertyChanged ("TestSelected");
        }
    }

    public MyViewModel ()
    {
        _myModel = new List<MyModel> ();
        BindingOperations.EnableCollectionSynchronization (_myModel, _lock);

        for (int i = 0; i < 10; i++)
        {
            _myModel.Add (new MyModel
            {
                Name = "Test " + i,
                Age = i * 22
            });
        }
        RaisePropertyChanged ("Model");
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged (string propertyName)
    {
        var pc = PropertyChanged;
        if (pc != null)
            pc (this, new PropertyChangedEventArgs (propertyName));
    }
}

Mon modele:

public class MyModel
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Et enfin, voici le code derrière MainWindow:

public partial class MainWindow : Window
{
    public MainWindow ()
    {
        InitializeComponent ();
        this.DataContext = new MyViewModel ();
    }
}

J'espère que cette conception propre de MVVM aide.

97
Sandesh

Ce que je ferais, c'est créer Behaviors en utilisant System.Windows.Interactivity. Vous devrez le référencer manuellement dans votre projet.

Avec un contrôle qui n'expose pas SelectedItems, par exemple, (ListBox, DataGrid)

Vous pouvez créer une classe de comportement quelque chose comme ça

public class ListBoxSelectedItemsBehavior : Behavior<ListBox>
{
    protected override void OnAttached()
    {
        AssociatedObject.SelectionChanged += AssociatedObjectSelectionChanged;
    }

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

    void AssociatedObjectSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var array = new object[AssociatedObject.SelectedItems.Count];
        AssociatedObject.SelectedItems.CopyTo(array, 0);
        SelectedItems = array;
    }

    public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.Register("SelectedItems", typeof(IEnumerable), typeof(ListBoxSelectedItemsBehavior), 
        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

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

Et sur votre XAML je ferais la Binding comme ceci où i est xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity" et behaviors est l'espace de noms de votre classe Behavior

<ListBox>
 <i:Interaction.Behaviors>
    <behaviors:ListBoxSelectedItemsBehavior SelectedItems="{Binding SelectedItems, Mode=OneWayToSource}" />
 </i:Interaction.Behaviors>

En supposant que votre DataContext pour la ListBox ait la propriété SelectedItems dans la ViewModel, elle mettra automatiquement à jour la SelectedItems. Vous avez encapsulé la event souscrivant à la View c'est-à-dire,

<ListBox SelectionChanged="ListBox_SelectionChanged"/>

Vous pouvez modifier la classe Behavior pour qu'elle soit de type DataGrid si vous le souhaitez.

26
123 456 789 0

J'utilise cette solution dans mon application:

XAML:

<i:Interaction.Triggers>
     <i:EventTrigger EventName="SelectionChanged">
         <i:InvokeCommandAction Command="{Binding SelectItemsCommand}" CommandParameter="{Binding Path=SelectedItems,ElementName=TestListView}"/>
     </i:EventTrigger>
</i:Interaction.Triggers>

en haut de votre fichier xaml, ajoutez cette ligne de code:

xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"

SelectedItemsCommand est un type ICommand qui est écrit dans votre modèle de vue.

DLL utilisée:

System.Windows.Interactivity.dll

17
Allen4Tech

Avec la variable DataGrid par défaut de WPF, il n'est pas possible d'utiliser une liaison, comme c'est possible avec la propriété SelectedItem-, car la propriété SelectedItems- n'est pas une propriété DependencyProperty.

Une façon d’obtenir ce que vous voulez est d’enregistrer l’événement SelectionChanged- du DataGrid pour mettre à jour la propriété de votre ViewModel, qui stocke les éléments sélectionnés. 

La propriété SelectedItems du DataGrid est de type IList, vous devez donc convertir les éléments de la liste en votre type spécifique.

C #

public MyViewModel {
  get{
    return this.DataContext as MyViewModel;
  }
}

private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) {
  // ... Get SelectedItems from DataGrid.
  var grid = sender as DataGrid;
  var selected = grid.SelectedItems;

  List<MyObject> selectedObjects = selected.OfType<MyObject>().ToList();

  MyViewModel.SelectedMyObjects = selectedObjects;
}

XAML

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
    <DataGrid
        SelectionChanged="DataGrid_SelectionChanged"
        />
    </Grid>
</Window>
9
Jehof

Vous pouvez maka a classe de base générique réutilisable . De cette façon, vous pouvez sélectionner des lignes à la fois dans le code et dans l'interface utilisateur .

Ceci est mon exemple de classe que je veux être sélectionnable

public class MyClass
{
    public string MyString {get; set;}   
}

Créer une classe de base générique pour les classes sélectionnables. INotifyPropertyChanged met à jour l'interface utilisateur lorsque vous définissez IsSelected.

public class SelectableItem<T> : System.ComponentModel.INotifyPropertyChanged
{
    public SelectableItem(T item)
    {
        Item = item;
    }

    public T Item { get; set; }

    bool _isSelected;

    public bool IsSelected {
        get {
            return _isSelected;
        }
        set {
            if (value == _isSelected)
            {
                return;
            }

            _isSelected = value;

            if (PropertyChanged != null)
            { 
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("IsSelected"));
            }
        }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
}

Créer une classe sélectionnable

public class MySelectableItem: SelectableItem<MyClass>
{
    public MySelectableItem(MyClass item)
       :base(item)
    {
    }
}

Créer une propriété à lier 

ObservableCollection<MySelectableItem> MyObservableCollection ...

Set propety 

MyObservableCollection = myItems.Select(x => new MySelectableItem(x));

Lier à datagrid et ajouter un style sur le DataGridRow qui se lie à la propriété IsSelected sur le MySelectedItem

<DataGrid  
    ItemsSource="{Binding MyObservableCollection}"
    SelectionMode="Extended">
    <DataGrid.Resources>
        <Style TargetType="DataGridRow">
            <Setter Property="IsSelected" Value="{Binding IsSelected}" />
        </Style>
    </DataGrid.Resources>
</DataGrid>

Pour obtenir les lignes/éléments sélectionnés 

var selectedItems = MyObservableCollection.Where(x=>x.IsSelected).Select(y=>y.Item);

Pour sélectionner des lignes/des éléments

MyObservableCollection[0].IsSelected = true;

Edit ———> Il semble que cela ne fonctionne pas lorsque EnableRowVirtualization est true.

2
AxdorphCoder

Vous pouvez ajouter la propriété "IsSelected" dans le modèle et ajouter un checkBox dans la ligne.

2
Angular_Learn

WPF DataGrid permet cela . Définissez simplement DataGrid.Rows.SelectionMode et DataGrid.Rows.SelectionUnit sur "Extended" et "CellOrRowHeader" respectivement. Cela peut être fait dans Blend, comme indiqué dans l'image que j'ai incluse. Cela permettra à l’utilisateur de sélectionner chaque cellule, des lignes entières, etc. autant de fois qu’ils le souhaitent, en utilisant soit la touche Maj, soit la touche Ctrl pour continuer à sélectionner .enter image description here

0
dylansweb

Le projet sur lequel je travaille utilise MVVM Light et j'ai trouvé ce blogpost est la solution la plus simple. Je vais répéter la solution ici:

Voir le modèle:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
...

public class SomeVm : ViewModelBase {

    public SomeVm() {
        SelectItemsCommand = new RelayCommand<IList>((items) => {
            Selected.Clear();
            foreach (var item in items) Selected.Add((SomeClass)item);
        });

        ViewCommand = new RelayCommand(() => {
            foreach (var selected in Selected) {
                // todo do something with selected items
            }
        });
    }

    public List<SomeClass> Selected { get; set; }
    public RelayCommand<IList> SelectionChangedCommand { get; set; }
    public RelayCommand ViewCommand { get; set; }
}

XAML:

<Window
    ...
    xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"
    xmlns:command="http://www.galasoft.ch/mvvmlight"
    ...
    <DataGrid
        Name="SomeGrid"
        ...
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <command:EventToCommand
                    Command="{Binding SelectionChangedCommand}"
                    CommandParameter="{Binding SelectedItems, ElementName=SomeGrid}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
        ...
        <DataGrid.ContextMenu>
            <ContextMenu>
                <MenuItem Header="View" Command="{Binding ViewCommand}" />
            </ContextMenu>
        </DataGrid.ContextMenu>
        ...
0
Seth Reno