Sélectionner plusieurs éléments d'une DataGrid dans un projet MVVM WPF
Comment puis-je sélectionner plusieurs éléments d'un DataGrid
dans un projet du FPF MVVM?
8 réponses
, Vous pouvez simplement ajouter un personnalisé propriété de dépendance 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
}
Maintenant vous pouvez utiliser ce dataGrid
dans le 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>
Mon 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 modèle:
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 MVVM aide.
ce que je ferais c'est créer Behaviors
en utilisant System.Windows.Interactivity
. Vous devez le référencer manuellement dans votre projet.
avec une commande qui n'expose pas SelectedItems
p.ex., (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 le Binding
comme ceci où i
est xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
et behaviors
est espace de noms de votre Behavior
classe
<ListBox>
<i:Interaction.Behaviors>
<behaviors:ListBoxSelectedItemsBehavior SelectedItems="{Binding SelectedItems, Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
en supposant que votre DataContext
pour le ListBox
a la propriété SelectedItems
dans le ViewModel
alors il mettra automatiquement à jour le SelectedItems
. Vous avez encapsulé le event
souscrivant du View
i.e.,
<ListBox SelectionChanged="ListBox_SelectionChanged"/>
vous pouvez changer la classe Behavior
pour être de type DataGrid
si vous voulez.
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>
au haut de votre fichier xaml, Ajoutez cette ligne de code:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
SelectedItemsCommand est le type ICommand qui est écrit dans votre modèle de vue.
DLL utilisée:
Système.Windows.Interactivité.dll
avec la valeur par défaut DataGrid
de WPF il n'est pas possible d'utiliser une reliure, car il est possible avec la propriété SelectedItem
-, car la propriété SelectedItems
- N'est pas une propriété de dépendance.
une façon d'accéder à ce que vous voulez est d'enregistrer le SelectionChanged
- Événement du DataGrid pour mettre à jour la propriété de votre ViewModel, qui stocke les éléments sélectionnés.
La propriété SelectedItems de la grille de données est de type IList donc vous devez mouler les éléments dans la liste à 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 ajouter la propriété" IsSelected " dans le Modèle et ajouter une case à cocher dans la rangée.
vous pouvez faire un réutilisable classe de base générique . De cette façon, vous pouvez sélectionner les lignes à la fois du code et de L'UI .
C'est ma classe d'exemple je veux être sélectionnable
public class MyClass
{
public string MyString {get; set;}
}
faire classe de base générique pour les classes sélectionnables. INotifyPropertyChanged fait la mise à jour de 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 classe sélectionnable
public class MySelectableItem: SelectableItem<MyClass>
{
public MySelectableItem(MyClass item)
:base(item)
{
}
}
Créer de la propriété de se 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é Issélectionnée 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 des lignes sélectionnées/articles
var selectedItems = MyObservableCollection.Where(x=>x.IsSelected).Select(y=>y.Item);
pour sélectionner les lignes /éléments
MyObservableCollection[0].IsSelected = true;
Edit - - -> Il semble que cela ne fonctionne pas lorsque la virtualisation Enablerow est vrai
WPF DataGrid permet cela. Il suffit de définir le DataGrid.Rangée.SelectionMode et DataGrid.Rangée.SelectionUnit à "Extended" et" CellOrRowHeader " respectivement. Cela peut être fait en mélange, comme montré dans l'image que j'ai inclus. Cela permettra à l'utilisateur de sélectionner chaque cellule, des rangées entières etc. autant qu'ils le souhaitent, en utilisant la touche shift ou ctrl pour continuer à sélectionner.
le projet sur lequel je travaille utilise MVVM Light et j'ai trouvé ce blog post pour être la solution la plus simple. Je répète la solution ici:
Modèle De Vue:
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>
...