c# wpf xaml mvvm datagrid

c# - Seleccione varios elementos de un DataGrid en un proyecto MVVM WPF



xaml (8)

Con el DataGrid predeterminado de WPF no es posible usar un enlace, ya que es posible con SelectedItem -Property, porque SelectedItems -Property no es DependencyProperty.

Una forma de obtener lo que desea es registrar SelectionChanged -Event de DataGrid para actualizar la propiedad de su ViewModel, que almacena los elementos seleccionados.

La propiedad SelectedItems de DataGrid es del tipo IList, por lo que debe convertir los elementos de la lista a su tipo específico.

DO#

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>

¿Cómo puedo seleccionar varios elementos de un DataGrid en un proyecto MVVM WPF?


El proyecto en el que estoy trabajando usa MVVM Light y encontré esta publicación de blog como la solución más simple. Voy a repetir la solución aquí:

Ver modelo:

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> ...


Lo que haría sería crear Behaviors utilizando System.Windows.Interactivity . Tendría que hacer referencia a ella manualmente en su proyecto.

Dado un control que no expone SelectedItems , por ejemplo, (ListBox, DataGrid)

Puedes crear una clase de comportamiento algo como esto

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

Y en su XAML haría la Binding esta manera, donde estoy xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" y los behaviors son el espacio de nombres de su clase de Behavior

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

Suponiendo que su DataContext para el ListBox tiene la propiedad SelectedItems en el ViewModel , automáticamente actualizará SelectedItems . Ha encapsulado el event suscribe desde la View , es decir,

<ListBox SelectionChanged="ListBox_SelectionChanged"/>

Puede cambiar la clase de Behavior para que sea de tipo DataGrid si lo desea.


Puede agregar la propiedad "IsSelected" en el Modelo y agregar un checkBox en la fila.


Puedes maka una clase base genérica reutilizable . De esta manera puede seleccionar filas tanto desde el código como desde la IU .

Esta es mi clase de ejemplo, quiero ser seleccionable

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

Haga una clase base genérica para las clases seleccionables. INotifyPropertyChanged realiza la actualización de la UI cuando establece 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; }

Crear clase seleccionable

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

Crear propiedad para enlazar a

ObservableCollection<MySelectableItem> MyObservableCollection ...

Establecer propety

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

Enlace a la cuadrícula de datos y agregue un estilo en el DataGridRow que se une a la propiedad IsSelected en MySelectedItem

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

Para obtener filas / artículos seleccionados

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

Para seleccionar filas / artículos

MyObservableCollection[0].IsSelected = true;

Editar ---> Parece que no funciona cuando EnableRowVirtualization es verdadero


Simplemente puede agregar una propiedad de dependencia personalizada para hacer esto:

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 }

Ahora puede usar este dataGrid en el 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>

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

Mi modelo:

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

Y finalmente, aquí está el código detrás de MainWindow :

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

Espero que este diseño MVVM limpio ayude.


Uso esta solución en mi aplicación:

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 la parte superior de su archivo xaml, agregue esta línea de código:

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

SelectedItemsCommand es el tipo de ICommand que está escrito en su viewmodel.

DLL usado:

System.Windows.Interactivity.dll


WPF DataGrid lo permite. Simplemente configure DataGrid.Rows.SelectionMode y DataGrid.Rows.SelectionUnit en "Extended" y "CellOrRowHeader", respectivamente. Esto se puede hacer en Blend, como se muestra en la imagen que he incluido. Esto le permitirá al usuario seleccionar cada celda, filas enteras, etc. tantas como quiera, usando la tecla shift o ctrl para continuar seleccionando.