Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

[C#/WPF] BindingList

On va s’écarter un peu de WP7 pour un article qui s’inscrit dans la lignée du “Après 8 ans de .NET, j’en découvre encore”.

Au programme, un problème assez frustrant que j’ai pu rencontrer à plusieurs reprise, dans des applications WPF. Commençons par poser les bases : nous avons une liste d’éléments, avec une propriété numérique (ici, “Count”). Nous voulons afficher ces éléments ainsi que la somme de leur “Count”, et permettre à l’utilisateur de les modifier individuellement (ce qui doit bien entendu mettre à jour la somme).

Pas de grande surprise au niveau du XAML (notez toutefois l’utilisation du fort pratique StringFormat pour le binding) :

   1: <Window x:Class="WpfApplication1.MainWindow"
   2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:         Title="MainWindow" Height="900" Width="500">
   5:     <StackPanel>
   6:         <TextBlock Text="{Binding Path=Total, StringFormat=Total: \{0\}}" />
   7:         
   8:         <ItemsControl ItemsSource="{Binding Path=Items}">
   9:             <ItemsControl.ItemTemplate>
  10:                 <DataTemplate>
  11:                     <TextBox Text="{Binding Path=Count}" />
  12:                 </DataTemplate>
  13:             </ItemsControl.ItemTemplate>
  14:         </ItemsControl>
  15:     </StackPanel>
  16: </Window>

Et la déclaration de la classe “Item” :

   1: public class Item : INotifyPropertyChanged
   2: {
   3:     private int count;
   4:  
   5:     public event PropertyChangedEventHandler PropertyChanged;
   6:  
   7:     public int Count
   8:     {
   9:         get
  10:         {
  11:             return this.count;
  12:         }
  13:  
  14:         set
  15:         {
  16:             this.count = value;
  17:             this.NotifyPropertyChanged("Count");
  18:         }
  19:     }
  20:  
  21:     protected void NotifyPropertyChanged(string propertyName)
  22:     {
  23:         var eventHandler = this.PropertyChanged;
  24:  
  25:         if (eventHandler != null)
  26:         {
  27:             eventHandler(this, new PropertyChangedEventArgs(propertyName));
  28:         }
  29:     }
  30: }

Dans le ViewModel, nous stockons les objets dans une ObservableCollection, afin que la vue soit automatiquement notifiée quand un item est ajouté ou retiré :

   1: public class ViewModel : INotifyPropertyChanged
   2: {
   3:     public ViewModel()
   4:     {
   5:         this.Items = new ObservableCollection<Item>();
   6:  
   7:         this.Items.Add(new Item { Count = 1 });
   8:         this.Items.Add(new Item { Count = 2 });
   9:         this.Items.Add(new Item { Count = 3 });
  10:         this.Items.Add(new Item { Count = 4 });
  11:  
  12:         this.ComputeSum();
  13:     }
  14:  
  15:     public event PropertyChangedEventHandler PropertyChanged;
  16:  
  17:     public ObservableCollection<Item> Items { get; set; }
  18:  
  19:     public int Total { get; protected set; }
  20:  
  21:     protected void ComputeSum()
  22:     {
  23:         this.Total = this.Items.Sum(i => i.Count);
  24:         this.NotifyPropertyChanged("Total");
  25:     }
  26:  
  27:     protected void NotifyPropertyChanged(string propertyName)
  28:     {
  29:         var eventHandler = this.PropertyChanged;
  30:  
  31:         if (eventHandler != null)
  32:         {
  33:             eventHandler(this, new PropertyChangedEventArgs(propertyName));
  34:         }
  35:     }
  36: }

A l’exécution, nous avons bien notre liste d’items qui s’affiche, ainsi que le total. Maintenant, comment faire pour qu’il se mette à jour ?

C’est là que la frustration commence. En effet, ObservableCollection permet d’être notifié lorsqu’un élément est ajouté ou supprimé, mais pas lorsqu’une des propriétés des éléments est modifiée, même si ceux-ci implémentent INotifyPropertChanged. Damned, comment ont-ils pu oublier un besoin aussi élémentaire ?

Du coup, comment faire ? Soit créer une nouvelle collection héritée d’ObservableCollection pour combler ce manque, soit faire toute la tambouille d’abonnement aux évènements “PropertyChanged” directement dans le ViewModel. Dans ce cas-ci, par souci de simplicité, optons pour la seconde solution :

   1: public class ViewModel : INotifyPropertyChanged
   2: {
   3:     public ViewModel()
   4:     {
   5:         this.Items = new ObservableCollection<Item>();
   6:  
   7:         this.Items.CollectionChanged += this.Items_CollectionChanged;
   8:  
   9:         this.Items.Add(new Item { Count = 1 });
  10:         this.Items.Add(new Item { Count = 2 });
  11:         this.Items.Add(new Item { Count = 3 });
  12:         this.Items.Add(new Item { Count = 4 });
  13:  
  14:         this.ComputeSum();
  15:     }
  16:  
  17:     public event PropertyChangedEventHandler PropertyChanged;
  18:  
  19:     public ObservableCollection<Item> Items { get; set; }
  20:  
  21:     public int Total { get; protected set; }
  22:  
  23:     protected void ComputeSum()
  24:     {
  25:         this.Total = this.Items.Sum(i => i.Count);
  26:         this.NotifyPropertyChanged("Total");
  27:     }
  28:  
  29:     protected void NotifyPropertyChanged(string propertyName)
  30:     {
  31:         var eventHandler = this.PropertyChanged;
  32:  
  33:         if (eventHandler != null)
  34:         {
  35:             eventHandler(this, new PropertyChangedEventArgs(propertyName));
  36:         }
  37:     }
  38:  
  39:     private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
  40:     {
  41:         if (e.OldItems != null)
  42:         {
  43:             foreach (INotifyPropertyChanged oldItem in e.OldItems)
  44:             {
  45:                 oldItem.PropertyChanged -= this.ItemPropertyChanged;
  46:             }
  47:         }
  48:  
  49:         if (e.NewItems != null)
  50:         {
  51:             foreach (INotifyPropertyChanged newItem in e.NewItems)
  52:             {
  53:                 newItem.PropertyChanged += this.ItemPropertyChanged;
  54:             }
  55:         }
  56:  
  57:         this.ComputeSum();
  58:     }    
  59:  
  60:     private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
  61:     {
  62:         if (e.PropertyName == "Count")
  63:         {
  64:             this.ComputeSum();
  65:         }
  66:     }
  67: }

Ca marche, mais laisse quand même sur sa faim : ce code manque clairement d’élégance.

Mais pas plus tard qu’aujourd’hui, je suis tombé, complètement par hasard, sur une discussion sur StackOverflow décrivant la classe “BindingList”. Il s’agit d’une ObservableCollection en plus puissant, puisqu’elle permet entre autres d’être notifié quand une propriété d’un des éléments est modifiée !

Adaptons donc notre code pour l’utiliser :

   1: public class ViewModel : INotifyPropertyChanged
   2: {
   3:     public ViewModel()
   4:     {
   5:         this.Items = new BindingList<Item>();
   6:             
   7:         this.Items.Add(new Item { Count = 1 });
   8:         this.Items.Add(new Item { Count = 2 });
   9:         this.Items.Add(new Item { Count = 3 });
  10:         this.Items.Add(new Item { Count = 4 });
  11:  
  12:         this.ComputeSum();
  13:  
  14:         this.Items.ListChanged += this.Items_ListChanged;
  15:     }
  16:  
  17:     public event PropertyChangedEventHandler PropertyChanged;
  18:  
  19:     public BindingList<Item> Items { get; set; }
  20:  
  21:     public int Total { get; protected set; }
  22:  
  23:     protected void ComputeSum()
  24:     {
  25:         this.Total = this.Items.Sum(i => i.Count);
  26:         this.NotifyPropertyChanged("Total");
  27:     }
  28:  
  29:     protected void NotifyPropertyChanged(string propertyName)
  30:     {
  31:         var eventHandler = this.PropertyChanged;
  32:  
  33:         if (eventHandler != null)
  34:         {
  35:             eventHandler(this, new PropertyChangedEventArgs(propertyName));
  36:         }
  37:     }
  38:  
  39:     private void Items_ListChanged(object sender, ListChangedEventArgs e)
  40:     {
  41:         if (e.ListChangedType != ListChangedType.ItemChanged
  42:             || (e.ListChangedType == ListChangedType.ItemChanged && e.PropertyDescriptor.Name == "Count"))
  43:         {
  44:             this.ComputeSum();
  45:         }
  46:     }
  47: }

Ca marche, et c’est autrement plus classe ;o)

Après vérification, cette classe existe depuis .NET 2.0, rien de neuf donc… Et pourtant !

Publié lundi 19 décembre 2011 20:48 par KooKiz
Classé sous : , ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

# re: [C#/WPF] BindingList

Regarde du côté de Rocky (Rockford Loktha) et de sa CLSA : la BindingList du framework est fortement inspiré de son code mais a été abandonnée par la suite (trop lourde et bogguée)

mardi 20 décembre 2011 07:47 by richardc
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Emportez votre sélection de la MSDN dans la poche ? par Blog de Jérémy Jeanson le il y a 3 heures et 43 minutes

- [ #Office365 ] Pb de connexion du flux Yammer ajouté à un site SharePoint par Le blog de Patrick [MVP SharePoint] le il y a 9 heures et 4 minutes

- NFluent & Data Annotations : coder ses propres assertions par Fathi Bellahcene le il y a 9 heures et 12 minutes

- Installer un site ASP.net 32bits sur un serveur exécutant SharePoint 2013 par Blog de Jérémy Jeanson le il y a 19 heures et 33 minutes

- [ SharePoint Summit 2014 ] Tests de montée en charge SharePoint par Le blog de Patrick [MVP SharePoint] le 04-16-2014, 20:44

- [ SharePoint Summit 2014 ] Bâtir un site web public avec Office 365 par Le blog de Patrick [MVP SharePoint] le 04-16-2014, 18:30

- Kinect + Speech Recognition + Eedomus = Dommy par Aurélien GALTIER le 04-16-2014, 17:17

- [ SharePoint Summit 2014 ] Une méthodologie simple pour concevoir vos applications OOTB SharePoint de A à Z par Le blog de Patrick [MVP SharePoint] le 04-16-2014, 16:51

- //Lean/ - Apprendre à faire des Apps Windows universelles par Blog de Jérémy Jeanson le 04-16-2014, 12:57

- Une culture de la donnée pour tous… par Le blog de Patrick [MVP SharePoint] le 04-16-2014, 11:00