IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Comprendre le Binding en WPF et Silverlight

Comprendre le Binding en WPF et Silverlight


précédentsommairesuivant

III. Les interfaces INotifyPropertyChanged et INotifyCollectionChanged

Observons la définition de cette interface et des types associés :

 
Sélectionnez
namespace System.ComponentModel
{
	public interface INotifyPropertyChanged
	{
		event PropertyChangedEventHandler PropertyChanged;
	}
  
	public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
  
	public class PropertyChangedEventArgs : EventArgs
	{
		public virtual string PropertyName { get; }
		public PropertyChangedEventArgs(string propertyName);        
	} 
}

On voit que cette interface est assez simple à implémenter : ce n'est qu'un évènement. Afin d'avoir plus de confort, il est préférable de créer une méthode de visibilité private ou protected afin de lever l'évènement. On peut ainsi créer une petite application similaire à la précédente.

 
Sélectionnez
<Window x:Class="BindingTutorial.WpfApplication.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>
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition />
		</Grid.RowDefinitions>
		<TextBox Text="{Binding Name}" />
		<Button Grid.Row="1"
				Content="Click Me"
				Click="ChangeName" />
		<ListBox Grid.Row="2"
				 Height="100"
				 ItemsSource="{Binding FavoriteMeals}" />
		<Button Grid.Row="3"
				Content="Click Me"
				Click="AddFavoriteMeal" />
	</Grid>
</Window>
 
Sélectionnez
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;

namespace BindingTutorial.WpfApplication
{
	public partial class MainWindow
	{
		private readonly Person _myPerson;

		public MainWindow()
		{
			InitializeComponent();
			_myPerson = new Person { Name = "Bobby", FavoriteMeals = new List<string> { "Pepperoni Pizza" } };
			DataContext = _myPerson;
		}

		private void ChangeName(object sender, RoutedEventArgs e)
		{
			_myPerson.Name = _myPerson.Name + _myPerson.Name;
			Trace.WriteLine("Changing name");
		}

		private void AddFavoriteMeal(object sender, RoutedEventArgs e)
		{
			_myPerson.FavoriteMeals.Add("Simple Pizza");
			Trace.WriteLine("Adding a favorite meal");
		}
	}

	public class Person : INotifyPropertyChanged
	{
		private string _name;
		private List<string> _favoriteMeals;

		public String Name
		{
			get { return _name; }
			set
			{
				_name = value;
				RaisePropertyChanged("Name");
			}
		}

		public List<String> FavoriteMeals
		{
			get { return _favoriteMeals; }
			set
			{
				_favoriteMeals = value;
				RaisePropertyChanged("FavoriteMeals");
			}
		}

		private void RaisePropertyChanged(String property)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(property));
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}
}

Au lieu de définir notre fenêtre comme contexte, on utilise cette fois un objet métier. On s'aperçoit donc que l'on implémente l'interface INotifyPropertyChanged et qu'on a implémenté une méthode RaisePropertyChanged pour lever l'évènement. Cette méthode est utilisée à chaque fois qu'on appelle le setter d'une propriété. Avec cette notification, l'interface graphique est capable de se rafraichir et ainsi le Binding de la TextBox reflète ce qu'il y a derrière, dans l'objet métier, y compris si le changement n'est pas initié par une saisie.
Cependant, l'ajout d'un item à notre collection ne fonctionne toujours pas, implémenter l'interface INotifyCollectionChanged est nécessaire. Observons la de plus près :

 
Sélectionnez
namespace System.Collections.Specialized
{
	public interface INotifyCollectionChanged
	{
		event NotifyCollectionChangedEventHandler CollectionChanged;
	}
	  
	public delegate void NotifyCollectionChangedEventHandler(object sender, NotifyCollectionChangedEventArgs e);

	public class NotifyCollectionChangedEventArgs : EventArgs
	{
		public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action);
		public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem);
		public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem, int index);
		public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems);
		public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems, int startingIndex);
		public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object newItem, object oldItem);
		public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object newItem, object oldItem, int index);
		public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList newItems, IList oldItems);
		public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList newItems, IList oldItems, int startingIndex);
		public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem, int index, int oldIndex);
		public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems, int index, int oldIndex);

		public NotifyCollectionChangedAction Action { get; }
		public IList NewItems { get; }
		public IList OldItems { get; }
		public int NewStartingIndex { get; }
		public int OldStartingIndex { get; }
	}
	
	public enum NotifyCollectionChangedAction
	{
		Add,
		Remove,
		Replace,
		Move,
		Reset,
	}
}

On s'aperçoit que bien que l'interface soit simple à implémenter, l'argument à passer pour l'évènement est un peu plus complexe à créer. En effet, cet évènement ne se contente pas de dire que la collection a changé mais il détaille en quoi elle a changé : qui a disparu, qui est apparu, qui s'est déplacé ou a été remplacé. En bref, c'est une logique assez complexe à implémenter. De plus, il est rarement une bonne idée de vouloir implémenter sa propre collection ! Les collections du framework sont suffisamment puissantes pour la plupart des besoins. C'est d'ailleurs une collection fournie avec le framework qui va nous intéresser : ObservableCollection<T>. Ce type implémente INotifyCollectionChanged mais aussi IEnumerable, IEnumerable<T>, ICollection, ICollection<T>, IList, IList<T> et INotifyPropertyChanged.
Si l'on remplace notre List<String> par une ObservableCollection<String>, on a bien une mise à jour de la ListBox lors du clic sur le second bouton !

 
Sélectionnez
public class Person : INotifyPropertyChanged
{
	private string _name;
	private ObservableCollection<string> _favoriteMeals;

	public String Name
	{
		get { return _name; }
		set
		{
			_name = value;
			RaisePropertyChanged("Name");
		}
	}

	public ObservableCollection<String> FavoriteMeals
	{
		get { return _favoriteMeals; }
		set
		{
			_favoriteMeals = value;
			RaisePropertyChanged("FavoriteMeals");
		}
	}

	private void RaisePropertyChanged(String property)
	{
		if (PropertyChanged != null)
			PropertyChanged(this, new PropertyChangedEventArgs(property));
	}

	public event PropertyChangedEventHandler PropertyChanged;
}

IV. Les DependencyProperties

Si on se penche d'un peu plus près sur les classes du framework, prenons par exemple un Slider, on s'aperçoit qu'elles n'implémentent pas INotifyPropertyChanged. Pourtant, lorsque l'on fait l'essai suivant, on s'aperçoit que changer la valeur du Slider change le contenu de la TextBox (et inversement).

 
Sélectionnez
<Window x:Class="BindingTutorial.WpfApplication.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>
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition />
		</Grid.RowDefinitions>
		<Grid.ColumnDefinitions>
			<ColumnDefinition />
			<ColumnDefinition />
		</Grid.ColumnDefinitions>
		<TextBox x:Name="myTextBox" Text="{Binding ElementName=mySlider, Path=Value, Mode=TwoWay}" />
		<Slider x:Name="mySlider" Grid.Column="1" />
	</Grid>
</Window>

Ces classes n'implémentant pas INotifyPropertyChanged, elles ont donc besoin d'un autre mécanisme de notification. Cet autre mécanisme vient de l'héritage par les contrôles de la classe DependencyObject. Cependant, bien que faire hériter les objets métier (ou ViewModels en MVVM) de cette classe soit possible, c'est rarement confortable ! Cette classe est plutôt héritée lorsque l'on développe des éléments graphiques.
Voici un exemple de déclaration de DependencyProperty

 
Sélectionnez
public class MyObject : DependencyObject
{
	public static readonly DependencyProperty MyPropProperty =
		DependencyProperty.Register("MyProp", typeof(String), typeof(MyObject), new PropertyMetadata(default(String)));

	public String MyProp
	{
		get { return (String)GetValue(MyPropProperty); }
		set { SetValue(MyPropProperty, value); Trace.WriteLine(String.Format("Set {0}", value)); }
	}
}

On peut voir que la DependencyProperty est un champ statique de la classe et que la propriété associée s'en sert comme paramètre des fonctions statiques GetValue et SetValue de DependencyObject.
Il est également possible de définir des AttachedDependencyProperties, ce sont des DependencyProperties que l'on peut attacher à d'autres DependencyObjects. C'est utile pour ajouter un champ "bindable" à un contrôle du framework par exemple. Plus d'info dans la doc MSDNMSDN Library


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2012 Nathanael Marchand. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.