Comment sélectionner plusieurs éléments d'une DataGrid
dans un projet MVVM WPF?
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.
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.
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
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>
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.
Vous pouvez ajouter la propriété "IsSelected" dans le modèle et ajouter un checkBox dans la ligne.
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 .
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>
...