web-dev-qa-db-fra.com

Re-trier WPF DataGrid après que les données liées aient changé

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 ?

26
marc wellman

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

27
marc wellman

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.

17
sellmeadog
2
AnjumSKhan

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} }" >...
1
Anthony Nichols

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>
0
Justin Pihony

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