Soyons à l’écoute les un des autres.
Parfois, il peut être utile de connaitre l’état de certaines propriétés de nos contrôles. Imaginez par exemple qu’en fonction de la visibilité d’un contrôle, vous devez faire un traitement quelconque.
Premier réflexe, utilisation d’un controller (cf. Création & Utilisation d’un Controller). Jusque là rien de bien méchant si ce n’est qu’il faut créer un controller pour chaque type que l’on souhaite surveiller ou avoir un controller de type object qu’il faudra par la suite caster pour récupérer la valeur surveillée.
Avec cette solution, dans tous les cas nous devons avoir :
- une classe controller
- une déclaration du controller en ressource Xaml
- un binding du controller sur la propriété du notre contrôle
- coté C# la récupération de l’instance du controller via les ressources puis l’abonnement à l’évènement de changement de valeur
de propriété.
Deuxième réflexe, utiliser une vue qui implémente l’interface INotifyPropertyChanged et la passer en DataContext de l’objet à observer. Cela peut vite devenir gênant si vous avez déjà une autre vue en Datacontext. Vous devrez alors agréger la vue métier et la vue de gestion de l’interface.
Essayons de prendre un autre angle. Le mot clé des précédentes solutions est : Binding.
Nous pouvons l’effectuer en code behind. Pour cela nous avons besoin du controle et de sa propriété surveillée et lier tout ceci à une propriété de dépendance. La propriété de dépedance doit être de même type que la propriété surveillé ou de type object, mieux être un generic.
Prenons une classe contenant cette propriété de dépendence generic, un évènement levé à la modification de celle-ci, secouons bien fort et nous obtiendrons ceci :
using System.Windows;
using System.Windows.Data;
namespace KL.SLApp.ValueChangedListener
{
/// <summary>
/// DependencyPropertyValueChangedHelper class.
/// You can use this class to be notified when property value changed.
/// </summary>
/// <typeparam name="T"></typeparam>
public class DependencyPropertyValueChangedHelper<T> : FrameworkElement
{
#region "Dependency Properties"
/// <summary>
/// ValueProperty DependencyProperty.
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(T), typeof(DependencyPropertyValueChangedHelper<T>), new PropertyMetadata(default(T), new PropertyChangedCallback(PropertyChangedCallBack)));
/// <summary>
/// Properties the changed call back.
/// </summary>
/// <param name="obj">The obj.</param>
/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private static void PropertyChangedCallBack(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((DependencyPropertyValueChangedHelper<T>)obj).OnPropertyValueChanged(e);
}
#endregion
#region "Constructor"
/// <summary>
/// Initializes a new instance of the <see cref="DependencyPropertyValueChangedHelper<T>"/> class.
/// </summary>
/// <param name="obj">The obj.</param>
/// <param name="propertyName">Name of the property.</param>
public DependencyPropertyValueChangedHelper(DependencyObject obj, string propertyName)
{
Binding bind = new Binding(propertyName);
bind.Source = obj;
bind.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(this, ValueProperty, bind);
}
#endregion
#region "Event"
/// <summary>
/// Occurs when [property value changed].
/// </summary>
public event DependencyPropertyChangedEventHandler PropertyValueChanged;
#endregion
#region "Properties"
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
public T Value
{
get
{
return (T)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
#endregion
#region "EventsHelper"
/// <summary>
/// Raises the <see cref="E:PropertyValueChanged"/> event.
/// </summary>
/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private void OnPropertyValueChanged(DependencyPropertyChangedEventArgs e)
{
if (PropertyValueChanged != null)
{
PropertyValueChanged(this, e);
}
}
#endregion
}
}
L’utilisation de cette classe se fait de la manière suivante :
private DependencyPropertyValueChangedHelper<Visibility> helper;
public MainPage()
{
InitializeComponent();
helper = new DependencyPropertyValueChangedHelper<Visibility>(spViewWithListener, "Visibility");
helper.PropertyValueChanged += new DependencyPropertyChangedEventHandler(Helper_PropertyValueChanged);
}
private void Helper_PropertyValueChanged(object sender, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show(string.Format("Listener event raised. Control visibility has changed !{0}New value = {1}", Environment.NewLine, e.NewValue.ToString()));
}
Et voilà ! Pour surveiller une autre propriété d’un autre contrôle il suffit de changer le type passé en generic et la propriété surveillée.
L’exemple ci-dessous permet de surveiller la propriété Background :
private DependencyPropertyValueChangedHelper<SolidColorBrush> helperColor;
public MainPage()
{
InitializeComponent();
helperColor = new DependencyPropertyValueChangedHelper<SolidColorBrush>(spViewWithListener, "Background");
helperColor.PropertyValueChanged += new DependencyPropertyChangedEventHandler(HelperColor_PropertyValueChanged);
}
private void HelperColor_PropertyValueChanged(object sender, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("Listener event raised. Control background color has changed.");
}
Pour résumer avec cette solution nous avons besoin :
- de la classe helper contenant la propriété de dépendance generic
- coté C#, l’instantiation du helper puis l’abonnement à l’évènement de changement de la valeur de la propriété.
A cela, plusieurs remarques :
- il faut faire attention à la manière dont on déclare le helper. Dans les exemples ci-dessus il est déclaré en attribut de la classe du contrôle pour que leur cycle de vie soient les même, ou presque. Si nous l’avions déclaré dans le constructeur de notre contrôle de la manière suivante :
public MainPage()
{
InitializeComponent();
DependencyPropertyValueChangedHelper<Visibility> helper = new DependencyPropertyValueChangedHelper<Visibility>(spViewWithListener, "Visibility");
helper.PropertyValueChanged += new DependencyPropertyChangedEventHandler(Helper_PropertyValueChanged);
}
sont cycle de vie commencerait dans le constructeur et se terminerait avec. L’existance de celui-ci dépendrait du garbage collector et nos aurions probablement des effets de bord.
Je le précise, car c’est une erreur que j’ai faite en construisant l’exemple téléchargeable ci-dessous
.
- Si vous souhaitez surveillez l’activité d’une collection (ajout, suppression, modification d’un élèment), par exemple la propriété Children d’un contrôle Grid. Cette solution ne fonctionnera pas.
Vous ne serez alerté que sur le changement de la référence Children donc sur l’assignation d’une nouvelle collection ou de la valeur null.
Conclusion :
Il n’y a plusieurs façons de répondre à un besoin. Choisir une méthode ou une autre dépend de bien des paramètres (délais, complexité, réutilisation, évolutions, …). La solution présenté ici peut paraître complexe par les mécanismes utilisés : binding, propriété de dépendance, generic, gestion d’évènements, mais elle me semble
plus séduisante par sa simplicité d’utilisation et réutilisabilité (mot presque plus facile à écrire qu’à prononcer) comparée à la solution des controlleurs ou de la vue.
Vous pouvez retrouver un exemple d’utilisation des controlleurs et un exemple d’utilisation du helper dans le projet suivant : [lien sur le projet].
Références :




