Je cherche un moyen de trier à nouveau ma DataGrid
lorsque les données sous-jacentes ont ont été modifiées .
(Le paramètre est assez standard: la propriété ItemSource
du DataGrid est liée à un ObservableCollection
; Les colonnes sont DataGridTextColumns
; Les données contenues dans le DataGrid réagissent correctement lorsque des modifications sont apportées à la ObservableCollection; le tri fonctionne bien lorsque vous cliquez dessus avec la souris.)
Des idées ?
Cela m'a pris toute l'après-midi mais j'ai finalement trouvé une solution qui est étonnamment simple, court et efficace:
Pour contrôler les comportements du contrôle d'interface utilisateur en question (ici, une variable DataGrid
), vous pouvez simplement utiliser une variable CollectionViewSource
. Il agit comme une sorte de représentant du contrôle de l'interface utilisateur dans votre ViewModel sans briser complètement le modèle MVMM.
Dans le ViewModel, déclarez une CollectionViewSource
et un ObservableCollection<T>
ordinaire et entourez la CollectionViewSource
autour de la ObservableCollection
:
// Gets or sets the CollectionViewSource
public CollectionViewSource ViewSource { get; set; }
// Gets or sets the ObservableCollection
public ObservableCollection<T> Collection { get; set; }
// Instantiates the objets.
public ViewModel () {
this.Collection = new ObservableCollection<T>();
this.ViewSource = new CollectionViewSource();
ViewSource.Source = this.Collection;
}
Ensuite, dans la partie Affichage de l'application, vous n'avez rien d'autre à faire pour lier la ItemsSource
de la CollectionControl
à la propriété View de la CollectionViewSource
au lieu d'être directement liée à la ObservableCollection
:
<DataGrid ItemsSource="{Binding ViewSource.View}" />
À partir de ce moment, vous pouvez utiliser l'objet CollectionViewSource
dans votre ViewModel pour manipuler directement le contrôle d'interface utilisateur dans la vue.
Le tri par exemple - comme cela a été mon principal problème - ressemblerait à ceci:
// Specify a sorting criteria for a particular column
ViewSource.SortDescriptions.Add(new SortDescription ("columnName", ListSortDirection.Ascending));
// Let the UI control refresh in order for changes to take place.
ViewSource.View.Refresh();
Vous voyez, très très simple et intuitif. J'espère que cela aide d'autres personnes comme ça m'a aidé.
Ceci est plus une clarification qu'une réponse, mais WPF always se lie à une variable ICollectionView
et non à la collection source. CollectionViewSource
est simplement un mécanisme utilisé pour créer/récupérer la vue de collection.
Voici une excellente ressource sur le sujet qui devrait vous aider à mieux utiliser les vues de collection dans WPF: http://bea.stollnitz.com/blog/?p=387
L'utilisation de CollectionViewSource
en XAML peut réellement simplifier votre code:
<Window.Resources>
<CollectionViewSource Source="{Binding MySourceCollection}" x:Key="cvs">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ColumnName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
...
<DataGrid ItemsSource="{Binding Source={StaticResource cvs}}">
</DataGrid>
Certaines personnes affirment que, lorsque vous suivez le modèle MVVM, le modèle de vue doit toujours exposer la vue de la collection, mais à mon avis, cela dépend du cas d'utilisation. Si le modèle de vue n'interagira jamais directement avec la vue de collection, il est simplement plus facile de le configurer en XAML.
Utilisez LiveSorting . Exemple de wpf.2000things.com .
La réponse de sellmeadog est trop compliquée ou obsolète. C'est super simple. Tout ce que tu dois faire est:
<UserControl.Resources>
<CollectionViewSource
Source="{Binding MyCollection}"
IsLiveSortingRequested="True"
x:Key="MyKey" />
</UserControl.Resources>
<DataGrid ItemsSource="{Binding Source={StaticResource MyKey} }" >...
Je ne vois pas de manière évidente de solution de facilité, alors je voudrais essayer un comportement attaché. C'est un peu une bâtarde, mais vous donnera ce que vous voulez:
public static class DataGridAttachedProperty
{
public static DataGrid _storedDataGrid;
public static Boolean GetResortOnCollectionChanged(DataGrid dataGrid)
{
return (Boolean)dataGrid.GetValue(ResortOnCollectionChangedProperty);
}
public static void SetResortOnCollectionChanged(DataGrid dataGrid, Boolean value)
{
dataGrid.SetValue(ResortOnCollectionChangedProperty, value);
}
/// <summary>
/// Exposes attached behavior that will trigger resort
/// </summary>
public static readonly DependencyProperty ResortOnCollectionChangedProperty =
DependencyProperty.RegisterAttached(
"ResortOnCollectionChangedProperty", typeof (Boolean),
typeof(DataGridAttachedProperty),
new UIPropertyMetadata(false, OnResortOnCollectionChangedChange));
private static void OnResortOnCollectionChangedChange
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
_storedDataGrid = dependencyObject as DataGrid;
if (_storedDataGrid == null)
return;
if (e.NewValue is Boolean == false)
return;
var observableCollection = _storedDataGrid.ItemsSource as ObservableCollection;
if(observableCollection == null)
return;
if ((Boolean)e.NewValue)
observableCollection.CollectionChanged += OnCollectionChanged;
else
observableCollection.CollectionChanged -= OnCollectionChanged;
}
private static void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.OldItems == e.NewItems)
return;
_storedDataGrid.Items.Refresh()
}
}
Ensuite, vous pouvez le joindre via:
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter
Property="AttachedProperties:DataGridAttachedProperty.ResortOnCollectionChangedProperty"
Value="true"
/>
</Style>
</DataGrid.Style>
Si vous avez une collection d'éléments INotifyPropertyChanged, vous pouvez l'utiliser à la place de ObservableCollection - elle s'actualise lorsque des éléments individuels de la collection changent: Note. : puisque ceci marque les éléments comme supprimés, puis réorganisés (même s'ils ne sont pas réellement supprimés et ajoutés), les sélections peuvent ne pas être synchronisées. C'est assez bon pour mes petits projets personnels, mais ce n'est pas prêt à être distribué aux clients ...
public class ObservableCollection2<T> : ObservableCollection<T>
{
public ObservableCollection2()
{
this.CollectionChanged += ObservableCollection2_CollectionChanged;
}
void ObservableCollection2_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
foreach (object o in e.OldItems)
remove(o);
if (e.NewItems != null)
foreach (object o in e.NewItems)
add(o);
}
void add(object o)
{
INotifyPropertyChanged ipc = o as INotifyPropertyChanged;
if(ipc!=null)
ipc.PropertyChanged += Ipc_PropertyChanged;
}
void remove(object o)
{
INotifyPropertyChanged ipc = o as INotifyPropertyChanged;
if (ipc != null)
ipc.PropertyChanged -= Ipc_PropertyChanged;
}
void Ipc_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs f;
f = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, sender);
base.OnCollectionChanged(f);
f = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
base.OnCollectionChanged(f);
}
}