Débutant

Les Dependency Properties

mbosL’accueil de Windows 8 a été des plus mitigés, confirmant la fameuse théorie du “un OS de Microsoft sur deux sera raté”. Certes, la plateforme n’a pas eu le succès escompté, mais elle a été un prélude essentiel au nouvel OS de Microsoft, sorti il y a peu, et qui rencontre bien plus de succès que son grand frère : Windows 10.
En effet, mises à part quelques nouveautés, le développement des applications Windows 10 reste semblable à celui des applications WinRT qui fonctionnaient sous Windows 8. Mais encore ?

Cet article, composé de deux parties distinctes, visera à vous familiariser avec le principe de Dependency Properties,  de User Controls et de Custom Controls.
Si à la sortie de Windows 8, nous avons pensé que les capacités de développement de la plateforme WinRT étaient limitées, c’était sans compter sur le pouvoir des CustomControls et de leurs Properties, qui représentent une vraie porte ouverte à la liberté de développement d’applications Windows.

Les plateformes de développement Microsoft sont multiples et chacune d’entre elles possède son propre langage de rendu graphique. Ici, nous nous intéresserons tout particulièrement au XAML, ou Extensive Application Markup Language, langage à balises hiérarchisées semblable au XML, utilisé notamment pour le WPF ou les applications Windows, que ce soit WinRT ou les fameuses Universal Apps.

XAML

XAML a fait son apparition en même temps que WPF 3.0, en 2006, au même titre que le databinding. Il a ensuite suivi son bonhomme de chemin au rythme des différentes versions de WPF, puis de Silverlight, sorti en 2007. La technologie, utilisée entre temps sur Windows Phone, a pris un coup de jus lors de la sortie de Windows 8 et des applications WinRT, et est toujours utilisée aujourd’hui pour les applications Windows 10 et Universal Apps, ces fameuses applications qui fonctionnent à la fois sur Windows 10 et Windows 10 Mobile.

XAML est un langage déclaratif basé sur XML ayant fait son apparition sous Windows Vista. Il a été utilisé depuis lors sous Windows 7, 8 et bientôt 10, principalement pour la création de vues, notamment en WPF, un système graphique utilisé pour le rendu des interfaces utilisateurs sur des applications bureaux basées sur Windows.

XAML est un dialecte XML permettant la déclaration, l’instanciation et l’exécution d’objets issus de la plateforme .NET depuis sa version 3.0. Aujourd’hui, il n’est plus seulement utilisé pour la création d’interfaces mais également dans des développements connexes comme par exemple WCF, le sous-système de communication de Microsoft.

Je parlais donc de contrôles et de properties. Tout bon développeur connaît bien entendu la définition et l’usage des properties, dans son langage orienté objet favori. XAML reprend le concept des properties, qui auront dans ses applications d’autant plus d’importance qu’elles seront au centre du rendu graphique d’une vue.

Aujourd’hui, beaucoup pensent que c’est donc un abus de considérer XAML comme un langage lié uniquement à la création d’interfaces. Mais c’est à cette facette que nous allons nous intéresser dans cet article.

L’emploi de cette technologie favorise la séparation du code métier et de l’interface graphique.
Il est vrai que, concernant son utilisation pour la plateforme WinRT, l’utilisation de XAML mettait beaucoup de barrières. Le binding, concept connu des développeurs et déjà présent sur WPF, n’était pas disponible sur toutes les propriétés d’un UIElement, le rendu graphique de certains contrôles n’était pas ergonomique… Microsoft a fait un bon bout de chemin pour en arriver à l’interface que nous connaissons aujourd’hui. De nouveaux contrôles sont apparus avec la sortie de Windows 10. XAML est devenu un langage facile à utiliser, offrant beaucoup d’opportunités et de liberté pour développer des applications ergonomiques et user friendly.

Les Properties

Une propriété est un mécanisme permettant l’accès et l’écriture d’un champ privé, au sein d’une classe. Elles sont utilisées comme s’il s’agissait de champs publics, mais ce ne sont que des méthodes simples appelées getters et setters.

 public class MyClass
 {
 private double _privateField; //champ privé
 public double Field //property de _field
 {
 get { return _privateField; }
 set { _privateField = value;* }
 }
 }

*value est ici un mot réservé pour désigner la nouvelle valeur que nous voulons attribuer au champ _privateField.

Exemple :

MyClass myClass = new MyClass();
myClass.Field = 1; //passe dans le setter "set" pour assigner la valeur 1 à _privateField
var my_var = myClass.Field // passe dans le getter, la variable "my_var" a maintenant la valeur 1

Il n’existe pas de norme de nommage imposée pour les properties. Par convention, le champ privé est écrit en minuscule et précédé d’un underscore (‘_’), et la property porte le même nom que ce dernier, mais sans underscore et avec une majuscule. Il s’agit ici d’une bonne pratique et d’une convention largement encouragée.

Binding et DataContext

Le binding, terme qui vient de bind, le lien, est le concept qui permet de tirer au mieux profit des properties. Il permet de bien séparer le code métier du rendu graphique, et d’appliquer bien plus facilement le pattern MVVM (Model View ViewModel, un pattern similaire à MVC).

En bindant le champ d’un contrôle XAML à une property, ce dernier se mettra automatiquement à jour, pour peu que le développeur ait implémenté l’interface INotifyPropertyChanged. Cette petite interface magique s’occupera toute seule de mettre à jour votre vue suivant les valeurs de vos properties bindées.

Nous nous éloignons un petit peu du sujet pour mieux y revenir par la suite. Que ce soit en WPF ou en Windows App (WinRT ou UWP), il existe le principe de DataContext. Chaque vue est liée à un environnement de variables et de méthodes appelé DataContext. C’est dans ce contexte de données que la vue ira piocher les informations dont elle a besoin.
En respectant le pattern MVVM, qui est de mise dans une application Windows, le DataContext de votre vue sera le ViewModel qui lui sera associé.
Pour plus d’informations sur les concepts listés ci-dessus, je vous invite à lire l’article de Wassim Azirar sur le framework MVVM Light où sont expliqués le pattern MVVM, les concepts de DataContext et l’utilisation de INotifyPropertyChanged.

Revenons à nos moutons : comment binder une valeur à une property ?

Prenons pour exemple un contrôle XAML TextBlock que vous voulez utiliser pour afficher une valeur.

<TextBlock x:Name="MyTextBlock" Text="{Binding MyValue}"/>

Conformément à l’utilisation de MVVM, votre page a pour DataContext le ViewModel que vous lui avez défini, que nous nommerons MyViewModel pour l’exemple.

public class MyViewModel
 {
      private string _myValue;
public string MyValue
 {
    get
    {
      return _myValue;
    }
    set
    {
      _myValue = value;
      RaisePropertyChanged ("MyValue");
    }
 }
}

 

RaisePropertyChanged est ici l’implémentation de l’interface INotifyPropertyChanged qui permet de notifier le changement de valeur à l’interface et donc de garder l’affichage à jour.

Lors de la génération de la page contenant MyTextBlock, le contrôle ira automatiquement chercher une valeur ayant pour nom “MyValue” dans son DataContext, qui est MyViewModel. Il rentrera de lui-même dans le getter, et résoudra la valeur de _myValue, qu’il affichera.

Il existe plusieurs modes de bindings:
TwoWay : Le DataContext met à jour le contrôle XAML, et le contrôle XAML met à jour le DataContext si sa valeur est changée indépendamment, dans le code behind par exemple. Si dans le code behind de la page, j’écris

This.MyTextBlock.Text = "Bonjour !";

Et que le binding sur MyTextBlock a été écrit de cette façon :

<TextBlock x:Name="MyTextBlock" Text="{Binding MyValue, Mode=TwoWay}"/>

Le TextBlock verra sa valeur “Text” mise à jour avec la valeur “Bonjour !”, et la source MyValue, dans le DataContext MyViewModel, appliquera la valeur “Bonjour !” à _myValue.

OneWay : mode utilisé par défaut si rien n’est spécifié. Met à jour le contrôle XAML lorsque la source change.

OneTime: met à jour le contrôle, mais seulement une fois, lorsque le DataContext est initialisé.

Pour en savoir plus sur le binding, je vous invite à lire l’article de Cyril Cathala sur le binding compilé.

Attention, si le binding semble magique, il a aussi ses limites. Faites attention au type de la property source que vous bindez à votre contrôle XAML. Si vous tentez, par exemple, de lier une string à un DateTimePicker, votre application lancera une exception lorsqu’elle tentera de résoudre le binding, car le type attendu est en réalité un DateTimeOffset.
Maintenant que nous sommes certains de parler de la même chose, nous pouvons passer aux choses un petit peu plus corsées !

Dependency Objects et Dependency Properties

Avant d’aborder les dependency properties, parlons des dependency objects. A quoi correspondent-ils ?
La classe DependencyObject héritant directement de Object est la base de beaucoup d’éléments UI comme UIElement, Style, Geometry, etc. Les dependency objects contiennent des dependency properties, des propriétés elles-mêmes définies par des méthodes. Un peu flou, n’est-ce pas ? Tout ceci sera plus clair avec la pratique. Et puis, à quoi servent-elles ?

Dependency properties

Basées sur le principe des properties que nous venons de voir, les dependency properties définissent donc des dependency objects et donc … des contrôles XAML. Vous pouvez créer vos propres contrôles XAML, from scratch ou dérivés de contrôles existants, et ainsi définir leur comportement avec des dependency properties. De plus, les dependency properties permettent aux objets d’avoir des propriétés dont la valeur peut dépendre de nombreuses choses, comme par exemple du data binding avec une autre propriété, ou d’une animation. Les dependency properties servent aussi à ajouter des traitements à effectuer sur les données.

Voyons déjà la syntaxe seule d’une dependency property, avant de nous lancer dans la manipulation de dependency objects. Une dependency property s’écrit de la façon suivante. Par convention, son nom se termine par le suffixe “Property” :

public static readonly DependencyProperty MyValueProperty=
 DependencyProperty.Register( "MyValue", typeof(string),
 typeof(MyClockControl), _propertyMetaData);
  • “MyValue” : nom de la propriété à laquelle est rattachée notre dependency property
  •  typeof(string) : type de la propriété
  • typeof(MyClockControl) : type du dependency object auquel est rattachée notre dependency property
  • _propertyMetaData : de type PropertyMetaData, permet de définir le comportement de la dependency property. Il est possible de mettre null si nous n’avons rien de particulier à spécifier ou mettre un “new
  • propertyMetaData(defaultValue) avec la valeur par défaut si souhaité.

Nous avons donc à côté :

public string MyValue
 {
 get { return (string)GetValue(MyValueProperty); }
 set { SetValue(MyValueProperty, value); }
 }

Mais quelle serait donc son utilisation concrète ? Voyons cela au sein d’un dependency object, un user control créé par nos soins.

public class MyUserControl: UserControl
 {
public readonly static DependencyProperty MyValueProperty= DependencyProperty.Register("MyValue", typeof(string), typeof(MyUserControl), new PropertyMetadata(""));
public float MyValue
 {
 get { return (string)GetValue(MyValueProperty); }
 set { SetValue(MyValueProperty, value); }
 }
}

Dans le XAML de la page où nous souhaitons afficher notre user control, nous aurons

><MyUserControl MyValue = "Bonjour !"/>

Attached properties

Les attached properties, ou propriétés attachées, peuvent être utilisées pour étendre ou enrichir le comportement d’un UIElement existant et appartenant au framework .NET.

Tout comme les dependency properties de base, les attached properties se définissent au sein d’un dependency object. Prenons pour exemple une attached property qui va effectuer une rotation sur un bouton.

public class RenderTransformManager : DependencyObject 
{
public static double GetAngle(DependencyObject obj) {
 return (double)obj.GetValue(AngleProperty);
 }
public static void SetAngle(DependencyObject obj, double value) {
 obj.SetValue(AngleProperty, value);
 }
public static readonly DependencyProperty AngleProperty = DependencyProperty.RegisterAttached("Angle",
typeof(double),
typeof(RenderTransformManager), new UIPropertyMetadata(0.0));
 }

 

Pour que l’attached property fasse quelque chose, le mieux est de lui ajouter une callback, ici OnAngleChanged.

public static readonly DependencyProperty AngleProperty = DependencyProperty.RegisterAttached("Angle",
 typeof(double),
typeof(RenderTransformManager),
new UIPropertyMetadata(0.0, OnAngleChanged));
 }
private static void OnAngleChanged(DependencyObject obj,  DependencyPropertyChangedEventArgs e)
 {
    var element = obj as UIElement;
    if(element != null) {
    element.RenderTransformOrigin = new Point(.5, .5);
    element.RenderTransform = new RotateTransform((double)e.NewVal    ue);
 }

Dès l’assignation de la property, la callback sera appelée et effectuera le traitement défini.

INotifyPropertyChanged est également concernée dans l’utilisation des dependency properties. Ces dernières vérifient si leur classe “propriétaire” implémente bel et bien l’interface, et si oui, permettent de garder la vue à jour en notifiant tout changement de la property liée.

Tout cela fait pas mal de choses pour une première partie. Nous avons donc vu la déclaration des dependency properties, des propriétés un petit peu particulières permettant de définir un depdendncy object, et s’abonnant automatiquement à INotifyPropertyChanged, nous avons également les attached properties, ces petites propriétés plus indépendantes qui permette d’enrichir des UIElement déjà présents dans le framework, mais comment utiliser tout cela de façon logique et concrète ? Les bases sont là, mais tout cela reste un peu flou, non ?

Dans la seconde partie, nous verrons la création et la mise en place de custom controls qui vous permettront de personnaliser vos applications.

Nombre de vue : 471

COMMENTAIRES 3 commentaires

  1. Paul CLEMENT dit :

    Bon premier article.
    Attention petite coquille sur la propriété MyValue que tu as déclarée en float et pas en string (paragraphe DEPENDENCY PROPERTIES, class MyUserControl)

  2. Marion Daussy dit :

    C’est changé, merci beaucoup !

  3. Benoit dit :

    Attention :

    public float MyValue
    {
    get { return (string)GetValue(MyValueProperty); }
    set { SetValue(MyValueProperty, value); }
    }

AJOUTER UN COMMENTAIRE