VOIR MA REPONSE AT LE FOND
Je fais juste une lecture légère sur WPF où je dois lier les éléments sélectionnés à partir d’un DataGrid mais je ne peux rien trouver de concret. J'ai juste besoin des objets sélectionnés.
Grille de données:
<DataGrid Grid.Row="5"
Grid.Column="0"
Grid.ColumnSpan="4"
Name="ui_dtgAgreementDocuments"
ItemsSource="{Binding Path=Documents, Mode=TwoWay}"
SelectedItem="{Binding Path=DocumentSelection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="White"
SelectionMode="Extended" Margin="2,5"
IsReadOnly="True"
CanUserAddRows="False"
CanUserReorderColumns="False"
CanUserResizeRows="False"
GridLinesVisibility="None"
HorizontalScrollBarVisibility="Hidden"
columnHeaderStyle="{StaticResource GreenTea}"
HeadersVisibility="Column"
BorderThickness="2"
BorderBrush="LightGray"
CellStyle="{StaticResource NonSelectableDataGridCellStyle}"
SelectionUnit="FullRow"
HorizontalContentAlignment="Stretch" AutoGenerateColumns="False">
Cela fonctionnera:
MultiSelectorBehaviours.vb
Imports System.Collections
Imports System.Windows
Imports System.Windows.Controls.Primitives
Imports System.Windows.Controls
Imports System
Public NotInheritable Class MultiSelectorBehaviours
Private Sub New()
End Sub
Public Shared ReadOnly SynchronizedSelectedItems As DependencyProperty = _
DependencyProperty.RegisterAttached("SynchronizedSelectedItems", GetType(IList), GetType(MultiSelectorBehaviours), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnSynchronizedSelectedItemsChanged)))
Private Shared ReadOnly SynchronizationManagerProperty As DependencyProperty = DependencyProperty.RegisterAttached("SynchronizationManager", GetType(SynchronizationManager), GetType(MultiSelectorBehaviours), New PropertyMetadata(Nothing))
''' <summary>
''' Gets the synchronized selected items.
''' </summary>
''' <param name="dependencyObject">The dependency object.</param>
''' <returns>The list that is acting as the sync list.</returns>
Public Shared Function GetSynchronizedSelectedItems(ByVal dependencyObject As DependencyObject) As IList
Return DirectCast(dependencyObject.GetValue(SynchronizedSelectedItems), IList)
End Function
''' <summary>
''' Sets the synchronized selected items.
''' </summary>
''' <param name="dependencyObject">The dependency object.</param>
''' <param name="value">The value to be set as synchronized items.</param>
Public Shared Sub SetSynchronizedSelectedItems(ByVal dependencyObject As DependencyObject, ByVal value As IList)
dependencyObject.SetValue(SynchronizedSelectedItems, value)
End Sub
Private Shared Function GetSynchronizationManager(ByVal dependencyObject As DependencyObject) As SynchronizationManager
Return DirectCast(dependencyObject.GetValue(SynchronizationManagerProperty), SynchronizationManager)
End Function
Private Shared Sub SetSynchronizationManager(ByVal dependencyObject As DependencyObject, ByVal value As SynchronizationManager)
dependencyObject.SetValue(SynchronizationManagerProperty, value)
End Sub
Private Shared Sub OnSynchronizedSelectedItemsChanged(ByVal dependencyObject As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If e.OldValue IsNot Nothing Then
Dim synchronizer As SynchronizationManager = GetSynchronizationManager(dependencyObject)
synchronizer.StopSynchronizing()
SetSynchronizationManager(dependencyObject, Nothing)
End If
Dim list As IList = TryCast(e.NewValue, IList)
Dim selector As Selector = TryCast(dependencyObject, Selector)
' check that this property is an IList, and that it is being set on a ListBox
If list IsNot Nothing AndAlso selector IsNot Nothing Then
Dim synchronizer As SynchronizationManager = GetSynchronizationManager(dependencyObject)
If synchronizer Is Nothing Then
synchronizer = New SynchronizationManager(selector)
SetSynchronizationManager(dependencyObject, synchronizer)
End If
synchronizer.StartSynchronizingList()
End If
End Sub
''' <summary>
''' A synchronization manager.
''' </summary>
Private Class SynchronizationManager
Private ReadOnly _multiSelector As Selector
Private _synchronizer As TwoListSynchronizer
''' <summary>
''' Initializes a new instance of the <see cref="SynchronizationManager"/> class.
''' </summary>
''' <param name="selector">The selector.</param>
Friend Sub New(ByVal selector As Selector)
_multiSelector = selector
End Sub
''' <summary>
''' Starts synchronizing the list.
''' </summary>
Public Sub StartSynchronizingList()
Dim list As IList = GetSynchronizedSelectedItems(_multiSelector)
If list IsNot Nothing Then
_synchronizer = New TwoListSynchronizer(GetSelectedItemsCollection(_multiSelector), list)
_synchronizer.StartSynchronizing()
End If
End Sub
''' <summary>
''' Stops synchronizing the list.
''' </summary>
Public Sub StopSynchronizing()
_synchronizer.StopSynchronizing()
End Sub
Public Shared Function GetSelectedItemsCollection(ByVal selector As Selector) As IList
If TypeOf selector Is MultiSelector Then
Return TryCast(selector, MultiSelector).SelectedItems
ElseIf TypeOf selector Is ListBox Then
Return TryCast(selector, ListBox).SelectedItems
Else
Throw New InvalidOperationException("Target object has no SelectedItems property to bind.")
End If
End Function
End Class
End Class
IListItemConverter.vb
''' <summary>
''' Converts items in the Master list to Items in the target list, and back again.
''' </summary>
Public Interface IListItemConverter
''' <summary>
''' Converts the specified master list item.
''' </summary>
''' <param name="masterListItem">The master list item.</param>
''' <returns>The result of the conversion.</returns>
Function Convert(ByVal masterListItem As Object) As Object
''' <summary>
''' Converts the specified target list item.
''' </summary>
''' <param name="targetListItem">The target list item.</param>
''' <returns>The result of the conversion.</returns>
Function ConvertBack(ByVal targetListItem As Object) As Object
End Interface
TwoListSynchronizer.vb
Imports System.Collections
Imports System.Collections.Specialized
Imports System.Linq
Imports System.Windows
''' <summary>
''' Keeps two lists synchronized.
''' </summary>
Public Class TwoListSynchronizer
Implements IWeakEventListener
Private Shared ReadOnly DefaultConverter As IListItemConverter = New DoNothingListItemConverter()
Private ReadOnly _masterList As IList
Private ReadOnly _masterTargetConverter As IListItemConverter
Private ReadOnly _targetList As IList
''' <summary>
''' Initializes a new instance of the <see cref="TwoListSynchronizer"/> class.
''' </summary>
''' <param name="masterList">The master list.</param>
''' <param name="targetList">The target list.</param>
''' <param name="masterTargetConverter">The master-target converter.</param>
Public Sub New(ByVal masterList As IList, ByVal targetList As IList, ByVal masterTargetConverter As IListItemConverter)
_masterList = masterList
_targetList = targetList
_masterTargetConverter = masterTargetConverter
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="TwoListSynchronizer"/> class.
''' </summary>
''' <param name="masterList">The master list.</param>
''' <param name="targetList">The target list.</param>
Public Sub New(ByVal masterList As IList, ByVal targetList As IList)
Me.New(masterList, targetList, DefaultConverter)
End Sub
Private Delegate Sub ChangeListAction(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
''' <summary>
''' Starts synchronizing the lists.
''' </summary>
Public Sub StartSynchronizing()
ListenForChangeEvents(_masterList)
ListenForChangeEvents(_targetList)
' Update the Target list from the Master list
SetListValuesFromSource(_masterList, _targetList, AddressOf ConvertFromMasterToTarget)
' In some cases the target list might have its own view on which items should included:
' so update the master list from the target list
' (This is the case with a ListBox SelectedItems collection: only items from the ItemsSource can be included in SelectedItems)
If Not TargetAndMasterCollectionsAreEqual() Then
SetListValuesFromSource(_targetList, _masterList, AddressOf ConvertFromTargetToMaster)
End If
End Sub
''' <summary>
''' Stop synchronizing the lists.
''' </summary>
Public Sub StopSynchronizing()
StopListeningForChangeEvents(_masterList)
StopListeningForChangeEvents(_targetList)
End Sub
''' <summary>
''' Receives events from the centralized event manager.
''' </summary>
''' <param name="managerType">The type of the <see cref="T:System.Windows.WeakEventManager"/> calling this method.</param>
''' <param name="sender">Object that originated the event.</param>
''' <param name="e">Event data.</param>
''' <returns>
''' true if the listener handled the event. It is considered an error by the <see cref="T:System.Windows.WeakEventManager"/> handling in WPF to register a listener for an event that the listener does not handle. Regardless, the method should return false if it receives an event that it does not recognize or handle.
''' </returns>
Public Function ReceiveWeakEvent(ByVal managerType As Type, ByVal sender As Object, ByVal e As EventArgs) As Boolean Implements System.Windows.IWeakEventListener.ReceiveWeakEvent
HandleCollectionChanged(TryCast(sender, IList), TryCast(e, NotifyCollectionChangedEventArgs))
Return True
End Function
''' <summary>
''' Listens for change events on a list.
''' </summary>
''' <param name="list">The list to listen to.</param>
Protected Sub ListenForChangeEvents(ByVal list As IList)
If TypeOf list Is INotifyCollectionChanged Then
CollectionChangedEventManager.AddListener(TryCast(list, INotifyCollectionChanged), Me)
End If
End Sub
''' <summary>
''' Stops listening for change events.
''' </summary>
''' <param name="list">The list to stop listening to.</param>
Protected Sub StopListeningForChangeEvents(ByVal list As IList)
If TypeOf list Is INotifyCollectionChanged Then
CollectionChangedEventManager.RemoveListener(TryCast(list, INotifyCollectionChanged), Me)
End If
End Sub
Private Sub AddItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
Dim itemCount As Integer = e.NewItems.Count
For i As Integer = 0 To itemCount - 1
Dim insertionPoint As Integer = e.NewStartingIndex + i
If insertionPoint > list.Count Then
list.Add(converter(e.NewItems(i)))
Else
list.Insert(insertionPoint, converter(e.NewItems(i)))
End If
Next
End Sub
Private Function ConvertFromMasterToTarget(ByVal masterListItem As Object) As Object
Return If(_masterTargetConverter Is Nothing, masterListItem, _masterTargetConverter.Convert(masterListItem))
End Function
Private Function ConvertFromTargetToMaster(ByVal targetListItem As Object) As Object
Return If(_masterTargetConverter Is Nothing, targetListItem, _masterTargetConverter.ConvertBack(targetListItem))
End Function
Private Sub HandleCollectionChanged(ByVal sender As Object, ByVal e As NotifyCollectionChangedEventArgs)
Dim sourceList As IList = TryCast(sender, IList)
Select Case e.Action
Case NotifyCollectionChangedAction.Add
PerformActionOnAllLists(AddressOf AddItems, sourceList, e)
Exit Select
Case NotifyCollectionChangedAction.Move
PerformActionOnAllLists(AddressOf MoveItems, sourceList, e)
Exit Select
Case NotifyCollectionChangedAction.Remove
PerformActionOnAllLists(AddressOf RemoveItems, sourceList, e)
Exit Select
Case NotifyCollectionChangedAction.Replace
PerformActionOnAllLists(AddressOf ReplaceItems, sourceList, e)
Exit Select
Case NotifyCollectionChangedAction.Reset
UpdateListsFromSource(TryCast(sender, IList))
Exit Select
Case Else
Exit Select
End Select
End Sub
Private Sub MoveItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
RemoveItems(list, e, converter)
AddItems(list, e, converter)
End Sub
Private Sub PerformActionOnAllLists(ByVal action As ChangeListAction, ByVal sourceList As IList, ByVal collectionChangedArgs As NotifyCollectionChangedEventArgs)
If sourceList Is _masterList Then
PerformActionOnList(_targetList, action, collectionChangedArgs, AddressOf ConvertFromMasterToTarget)
Else
PerformActionOnList(_masterList, action, collectionChangedArgs, AddressOf ConvertFromTargetToMaster)
End If
End Sub
Private Sub PerformActionOnList(ByVal list As IList, ByVal action As ChangeListAction, ByVal collectionChangedArgs As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
StopListeningForChangeEvents(list)
action(list, collectionChangedArgs, converter)
ListenForChangeEvents(list)
End Sub
Private Sub RemoveItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
Dim itemCount As Integer = e.OldItems.Count
' for the number of items being removed, remove the item from the Old Starting Index
' (this will cause following items to be shifted down to fill the hole).
For i As Integer = 0 To itemCount - 1
list.RemoveAt(e.OldStartingIndex)
Next
End Sub
Private Sub ReplaceItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
RemoveItems(list, e, converter)
AddItems(list, e, converter)
End Sub
Private Sub SetListValuesFromSource(ByVal sourceList As IList, ByVal targetList As IList, ByVal converter As Converter(Of Object, Object))
StopListeningForChangeEvents(targetList)
targetList.Clear()
For Each o As Object In sourceList
targetList.Add(converter(o))
Next
ListenForChangeEvents(targetList)
End Sub
Private Function TargetAndMasterCollectionsAreEqual() As Boolean
Return _masterList.Cast(Of Object)().SequenceEqual(_targetList.Cast(Of Object)().[Select](Function(item) ConvertFromTargetToMaster(item)))
End Function
''' <summary>
''' Makes sure that all synchronized lists have the same values as the source list.
''' </summary>
''' <param name="sourceList">The source list.</param>
Private Sub UpdateListsFromSource(ByVal sourceList As IList)
If sourceList Is _masterList Then
SetListValuesFromSource(_masterList, _targetList, AddressOf ConvertFromMasterToTarget)
Else
SetListValuesFromSource(_targetList, _masterList, AddressOf ConvertFromTargetToMaster)
End If
End Sub
''' <summary>
''' An implementation that does nothing in the conversions.
''' </summary>
Friend Class DoNothingListItemConverter
Implements IListItemConverter
''' <summary>
''' Converts the specified master list item.
''' </summary>
''' <param name="masterListItem">The master list item.</param>
''' <returns>The result of the conversion.</returns>
Public Function Convert(ByVal masterListItem As Object) As Object Implements IListItemConverter.Convert
Return masterListItem
End Function
''' <summary>
''' Converts the specified target list item.
''' </summary>
''' <param name="targetListItem">The target list item.</param>
''' <returns>The result of the conversion.</returns>
Public Function ConvertBack(ByVal targetListItem As Object) As Object Implements IListItemConverter.ConvertBack
Return targetListItem
End Function
End Class
End Class
Ensuite pour le XAML:
<DataGrid ..... local:MultiSelectorBehaviours.SynchronizedSelectedItems="{Binding SelectedResults}" />
Et enfin la VM:
Public ReadOnly Property SelectedResults As ObservableCollection(Of StatisticsResultModel)
Get
Return _objSelectedResults
End Get
End Property
Le crédit va à: http://blog.functionalfun.net/2009/02/how-to-databind-to-selecteditems.html
Je peux vous assurer que: SelectedItemsest en effet susceptible d'être lié en tant que XAML CommandParameter
Après beaucoup de recherches et de recherches sur Google, j'ai finalement trouvé une solution simple à ce problème courant.
Pour que cela fonctionne, vous devez suivre TOUTES les règles suivantes:
Suivant suggestion d'Ed Ball ', sur votre liaison de données de commande XAML, définissez CommandParameterproperty BEFORE Commandproperty. C'est un bogue qui prend beaucoup de temps.
Assurez-vous que vos méthodes ICommandCanExecuteet Executeont pour paramètre) la valeur objecttype. Vous pouvez ainsi empêcher les exceptions de conversion silencieuses qui se produisent chaque fois que la liaison de données CommandParametertype ne correspond pas au type de paramètre de votre méthode de commande.
private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)
{
// Your code goes here
}
private bool OnDeleteSelectedItemsExecute(object SelectedItems)
{
// Your code goes here
}
Par exemple, vous pouvez soit vous envoyer une SelectedItemsproperty) d'une listview/listbox ICommandmethods ou la listview/listbox elle-même. Super, n'est-ce pas?
J'espère que cela empêchera quelqu'un de passer autant de temps que moi à comprendre comment recevoir SelectedItemsas CanExecuteparamètre).
Ma réponse d'origine était fausse (vous ne pouvez pas vous lier à SelectedItems
car il s'agit d'une propriété en lecture seule).
Un moyen de contourner ce problème relativement simple à MVVM consiste à lier la propriété IsSelected
de DataGridRow
.
Vous pouvez configurer la liaison comme ceci:
<DataGrid ItemsSource="{Binding DocumentViewModels}"
SelectionMode="Extended">
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected"
Value="{Binding IsSelected}" />
</Style>
</DataGrid.Resources>
</DataGrid>
Vous devez ensuite créer une DocumentViewModel
qui hérite de ViewModelBase
(ou de la classe de base MVVM que vous utilisez) et qui possède les propriétés de votre Document
que vous souhaitez présenter dans le DataGrid, ainsi qu'une propriété IsSelected
.
Ensuite, dans votre modèle d’affichage principal, vous créez une List(Of DocumentViewModel)
appelée DocumentViewModels
pour lier votre DataGrid
à. (Remarque: si vous souhaitez ajouter/supprimer des éléments de la liste, utilisez plutôt ObservableCollection(T)
.)
Maintenant, voici la partie la plus délicate. Vous devez vous connecter à l'événement PropertyChanged
de chaque DocumentViewModel
dans votre liste, comme ceci:
For Each documentViewModel As DocumentViewModel In DocumentViewModels
documentViewModel.PropertyChanged += DocumentViewModel_PropertyChanged
Next
Cela vous permet de réagir aux modifications de toutes les variables DocumentViewModel
.
Enfin, dans DocumentViewModel_PropertyChanged
, vous pouvez parcourir votre liste (ou utiliser une requête Linq) pour récupérer les informations relatives à chaque élément où IsSelected = True
.
Avec un peu de ruse, vous pouvez étendre le DataGrid pour créer une version pouvant être liée de la propriété SelectedItems
. Ma solution nécessite que la liaison ait Mode=OneWayToSource
car je ne souhaite lire que de la propriété de toute façon, mais il pourrait être possible d’étendre ma solution pour permettre à la propriété d’être en lecture-écriture.
Je suppose qu'une technique similaire pourrait être utilisée pour ListBox, mais je ne l'ai pas essayée.
public class BindableMultiSelectDataGrid : DataGrid
{
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(BindableMultiSelectDataGrid), new PropertyMetadata(default(IList)));
public new IList SelectedItems
{
get { return (IList)GetValue(SelectedItemsProperty); }
set { throw new Exception("This property is read-only. To bind to it you must use 'Mode=OneWayToSource'."); }
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
SetValue(SelectedItemsProperty, base.SelectedItems);
}
}
Je suis venu ici pour la réponse et j'en ai eu plusieurs excellentes. Je les ai tous combinés dans une propriété attachée, très similaire à celle proposée par Omar ci-dessus, mais dans une classe. Gère INotifyCollectionChanged et fait sortir la liste. Ne fuit pas les événements. Je l'ai écrit pour que ce soit un code assez simple à suivre. Écrit en C #, gère les éléments sélectionnés de la boîte de sélection et les éléments sélectionnés de la grille de données.
Cela fonctionne à la fois sur DataGrid et ListBox.
(Je viens d'apprendre à utiliser GitHub) GitHub https://github.com/ParrhesiaJoe/SelectedItemsAttachedWpf
Utiliser:
<ListBox ItemsSource="{Binding MyList}" a:Ex.SelectedItems="{Binding ObservableList}"
SelectionMode="Extended"/>
<DataGrid ItemsSource="{Binding MyList}" a:Ex.SelectedItems="{Binding OtherObservableList}" />
Et voici le code. Il y a un petit échantillon sur Git.
public class Ex : DependencyObject
{
public static readonly DependencyProperty IsSubscribedToSelectionChangedProperty = DependencyProperty.RegisterAttached(
"IsSubscribedToSelectionChanged", typeof(bool), typeof(Ex), new PropertyMetadata(default(bool)));
public static void SetIsSubscribedToSelectionChanged(DependencyObject element, bool value) { element.SetValue(IsSubscribedToSelectionChangedProperty, value); }
public static bool GetIsSubscribedToSelectionChanged(DependencyObject element) { return (bool)element.GetValue(IsSubscribedToSelectionChangedProperty); }
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached(
"SelectedItems", typeof(IList), typeof(Ex), new PropertyMetadata(default(IList), OnSelectedItemsChanged));
public static void SetSelectedItems(DependencyObject element, IList value) { element.SetValue(SelectedItemsProperty, value); }
public static IList GetSelectedItems(DependencyObject element) { return (IList)element.GetValue(SelectedItemsProperty); }
/// <summary>
/// Attaches a list or observable collection to the grid or listbox, syncing both lists (one way sync for simple lists).
/// </summary>
/// <param name="d">The DataGrid or ListBox</param>
/// <param name="e">The list to sync to.</param>
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ListBox || d is MultiSelector))
throw new ArgumentException("Somehow this got attached to an object I don't support. ListBoxes and Multiselectors (DataGrid), people. Geesh =P!");
var selector = (Selector)d;
var oldList = e.OldValue as IList;
if (oldList != null)
{
var obs = oldList as INotifyCollectionChanged;
if (obs != null)
{
obs.CollectionChanged -= OnCollectionChanged;
}
// If we're orphaned, disconnect lb/dg events.
if (e.NewValue == null)
{
selector.SelectionChanged -= OnSelectorSelectionChanged;
SetIsSubscribedToSelectionChanged(selector, false);
}
}
var newList = (IList)e.NewValue;
if (newList != null)
{
var obs = newList as INotifyCollectionChanged;
if (obs != null)
{
obs.CollectionChanged += OnCollectionChanged;
}
PushCollectionDataToSelectedItems(newList, selector);
var isSubscribed = GetIsSubscribedToSelectionChanged(selector);
if (!isSubscribed)
{
selector.SelectionChanged += OnSelectorSelectionChanged;
SetIsSubscribedToSelectionChanged(selector, true);
}
}
}
/// <summary>
/// Initially set the selected items to the items in the newly connected collection,
/// unless the new collection has no selected items and the listbox/grid does, in which case
/// the flow is reversed. The data holder sets the state. If both sides hold data, then the
/// bound IList wins and dominates the helpless wpf control.
/// </summary>
/// <param name="obs">The list to sync to</param>
/// <param name="selector">The grid or listbox</param>
private static void PushCollectionDataToSelectedItems(IList obs, DependencyObject selector)
{
var listBox = selector as ListBox;
if (listBox != null)
{
if (obs.Count > 0)
{
listBox.SelectedItems.Clear();
foreach (var ob in obs) { listBox.SelectedItems.Add(ob); }
}
else
{
foreach (var ob in listBox.SelectedItems) { obs.Add(ob); }
}
return;
}
// Maybe other things will use the multiselector base... who knows =P
var grid = selector as MultiSelector;
if (grid != null)
{
if (obs.Count > 0)
{
grid.SelectedItems.Clear();
foreach (var ob in obs) { grid.SelectedItems.Add(ob); }
}
else
{
foreach (var ob in grid.SelectedItems) { obs.Add(ob); }
}
return;
}
throw new ArgumentException("Somehow this got attached to an object I don't support. ListBoxes and Multiselectors (DataGrid), people. Geesh =P!");
}
/// <summary>
/// When the listbox or grid fires a selectionChanged even, we update the attached list to
/// match it.
/// </summary>
/// <param name="sender">The listbox or grid</param>
/// <param name="e">Items added and removed.</param>
private static void OnSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var dep = (DependencyObject)sender;
var items = GetSelectedItems(dep);
var col = items as INotifyCollectionChanged;
// Remove the events so we don't fire back and forth, then re-add them.
if (col != null) col.CollectionChanged -= OnCollectionChanged;
foreach (var oldItem in e.RemovedItems) items.Remove(oldItem);
foreach (var newItem in e.AddedItems) items.Add(newItem);
if (col != null) col.CollectionChanged += OnCollectionChanged;
}
/// <summary>
/// When the attached object implements INotifyCollectionChanged, the attached listbox
/// or grid will have its selectedItems adjusted by this handler.
/// </summary>
/// <param name="sender">The listbox or grid</param>
/// <param name="e">The added and removed items</param>
private static void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Push the changes to the selected item.
var listbox = sender as ListBox;
if (listbox != null)
{
listbox.SelectionChanged -= OnSelectorSelectionChanged;
if (e.Action == NotifyCollectionChangedAction.Reset) listbox.SelectedItems.Clear();
else
{
foreach (var oldItem in e.OldItems) listbox.SelectedItems.Remove(oldItem);
foreach (var newItem in e.NewItems) listbox.SelectedItems.Add(newItem);
}
listbox.SelectionChanged += OnSelectorSelectionChanged;
}
var grid = sender as MultiSelector;
if (grid != null)
{
grid.SelectionChanged -= OnSelectorSelectionChanged;
if (e.Action == NotifyCollectionChangedAction.Reset) grid.SelectedItems.Clear();
else
{
foreach (var oldItem in e.OldItems) grid.SelectedItems.Remove(oldItem);
foreach (var newItem in e.NewItems) grid.SelectedItems.Add(newItem);
}
grid.SelectionChanged += OnSelectorSelectionChanged;
}
}
}
Je sais que ce post est un peu vieux et a été répondu. Mais je suis venu avec une réponse non MVVM. C'est facile et fonctionne pour moi. Ajoutez un autre DataGrid, en supposant que votre collection sélectionnée est SelectedResults:
<DataGrid x:Name="SelectedGridRows"
ItemsSource="{Binding SelectedResults,Mode=OneWayToSource}"
Visibility="Collapsed" >
Et dans le code derrière, ajoutez ceci au constructeur:
public ClassConstructor()
{
InitializeComponent();
OriginalGrid.SelectionChanged -= OriginalGrid_SelectionChanged;
OriginalGrid.SelectionChanged += OriginalGrid_SelectionChanged;
}
private void OriginalGrid_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
SelectedGridRows.ItemsSource = OriginalGrid.SelectedItems;
}
J'ai une solution pour cela en utilisant une solution de contournement qui répond à mes besoins.
Créez une EventToCommand
sur la ListItemTemplate
sur MouseUp
et, en tant que CommandParameter
, envoyez la collection SelectedItems
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseUp">
<helpers:EventToCommand Command="{Binding DataContext.SelectionChangedUpdate,
RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding ElementName=personsList, Path=SelectedItems}" />
</i:EventTrigger>
</i:Interaction.Triggers>
De cette façon, vous pouvez avoir une commande dans le modèle de vue qui gère cela ou enregistre les éléments sélectionnés pour une utilisation ultérieure. Amusez-vous à coder
Pour moi, le moyen le plus simple était de renseigner la propriété ViewModel dans l'événement SelectionChanged.
private void MyDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
(DataContext as MyViewModel).SelectedItems.Clear();
foreach (var i in MyDataGrid.SelectedItems) (DataContext as MyViewModel).SelectedItems.Add(i as ItemType);
}
Qu'en est-il d'utiliser un convertisseur?
public class SelectedItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var dg = value as DataGrid;
return dg?.SelectedItems;
}
...
Utilisez-le dans une DataGrid
's ContextMenu
comme ceci:
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem
CommandParameter="{Binding Path=MySelectedItems, Converter={StaticResource selectedItemsConverter}, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
Command="{Binding MyDoSomethingWithMySelectedItemsCommand}"
Header="Do Something...">
</MenuItem>
La reliure regarde directement le modèle, version compliquée:
1) Créer ICommand:
public class GetSelectedItemsCommand : ICommand
{
public GetSelectedItemsCommand(Action<object> action)
{
_action = action;
}
private readonly Action<object> _action;
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action(parameter);
}
}
2) Créer un DataGrid
<DataGrid x:Name="DataGridOfDesperatePeople" SelectionMode="Extended">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction CommandParameter="{Binding ElementName=DataGridOfDesperatePeople, Path=SelectedItems}" Command="{Binding SelectedItemsCommand }" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
3) Créer dans le modèle de vue
public List<YourClassOfItemInTheGrid> SelectedItems { get; set; } = new List<YourClassOfItemInTheGrid>();
public ICommand SelectedItemsCommand
{
get
{
return new GetSelectedItemsCommand(list =>
{
SelectedItems.Clear();
IList items = (IList)list;
IEnumerable<YourClassOfItemInTheGrid> collection = items.Cast<YourClassOfItemInTheGrid>();
SelectedItems = collection.ToList();
});
}
}