Composants : Custom Control

Dans l’univers des composants WPF ont entend parlé de composants pour évoquer principales deux choses : les UserControl et les CustomControl. Alors que les premiers permettent de créer un composant graphique sur mesure, les seconds offrent une personnalisation d’un composant existant.

Control

Un CustomControl est tout simplement un Control dont le comportement a été personnalisé pour les besoins spécifique d’une application. Ce dernier est la base de tous les éléments graphiques contrôlables par le développeur.

Elle constitue la classe mère dont dérives les éléments de l’interface graphiques manipulable dans une application WPF. Elle est d’ailleurs décrite comme  » Représentant la classe de base pour l’interface utilisateur (UI) « .

public class TextBox : TextBoxBase... {...}
public abstract class TextBoxBase : Control {...}
public class Button : ButtonBase {...}
public abstract class ButtonBase : ContentControl... {...}
public class ContentControl : Control {...}
public class ListView : ListBox {...}
public class ListBox : Selector {...}
public abstract class Selector : ItemsControl {...}
public class ItemsControl : Control... {...}

Personnalisation

La personnalisation constitue donc le fait de créer un nouveau Control qui dérivera de la classe mère en question ou, plus généralement, d’une classe enfant proche de nos besoins.

Prenons par exemple une PostalBox qui, au lieu de contenir un texte générique, contiendrait spécifique un code postal. Pour simplifier l’exemple, on va considérer que ce qui constitue son contenu n’est qu’une restriction à 5 caractères.

public class PostalBox : TextBox

Ainsi, nous allons hérité des attributs de base de la class TextBox dont PostalBox dérive, à savoir Text. Mais ici, nous allons volontairement avoir besoin que PostalBox.Text n’ait une valeur qu’à partir du moment qu’on a saisi 5 charactères.

Ensuite on pourra l’utiliser dans une page/fenetre/control xaml qui référencera l’espace de nom où il est situé de cette façon :

<local:PostalBox x:Name="testPbx" Width="50" Height="30" Text=""/>

Text²

Notre première action sera donc d’ovverider sa propriété Text (lié à une DependencyProperty, mais passons), en la redefinissant.

public new string Text
{
get;
set;
}

De cette manière, notre PostalBox aura en fait deux propriété Text : l’une accessible via son parent TextBox (accessible donc via un cast, ou un base.Text depuis la classe) et sa propre propriété Text. Ainsi la valeur saisie ira se stocket dans le TextBox.Text tandis que le PostalBox.Text restera vide.

2TEXT.gif

De ce fait, même si l’affichage de la PostalBox affichera le texte saisi, chercher à le récupérer depuis testPbx.Text ne donnera rien.

Donc, il va falloir chercher à récupérer la propriété Text du parent (TextBox) et à remplir la propriété Text de l’enfant (PostalBox). Problème : comme évoqué, « Text » fonctionne en réalité avec une DependencyProperty. Il va donc falloir intercepté son comportement habituel pour enrichir la nouvelle propriété Text.

OverrideMetadata

Notre TextBox possède un membre Text.
Ce dernier est en fait une combinaisons d’une Property « Text » et d’une DependencyProperty « TextProperty ». De base, cette Property pourrait se présenter comme ça :

public string Text
{
   get { return (string)GetValue(TextProperty); }
   set { SetValue(TextProperty, value); }
}

public static readonly DependencyProperty TextProperty =
   DependencyProperty.Register("Text", typeof(string), typeof(TextBox), new PropertyMetadata("",TextPropertyChangedCallback));

private static void TextPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e){...}

Ici, la Property Text retourne la valeur de la DependencyProperty TextProperty. Cette dernière, lié à un fonctionnement étudié notamment pour faciliter les binding, prend en argument une méthode callback apellée à chaque modification de la donnée.

Néanmoins impossible là de remplacer la Methode de callback : si on redéfinit la DépendencyProperty TextProperty, elle ne sera utilisée qu’au sein de cette classe, et non dans le fonctionnement de la classe parente naturellement apellée à la complétion de la PostalBox.

Il va donc falloir Overrider le fonctionnement de la DependencyProperty TextBox.TextProperty.

var defaultMetadata = TextBox.TextProperty.GetMetadata(typeof(TextBox));

TextBox.TextProperty.OverrideMetadata(
   typeof(PostalBox),
   new FrameworkPropertyMetadata("", new PropertyChangedCallback(NewTextPropertyChanged)) 
);

De cette manière notre methode NewTextPropertyChanged prendra le dessus.
Ainsi, il sera possible d’y faire le traitement désiré pour completer PostalBox.Text.

private void NewTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
   if(base.Text != null && base.Text.Length > 5)
   {
      Text = base.Text;
   }
   else
   {
      Text = null;
   }
}

Le reste de notre application pourra récupérer la donnée de la PostalBox.Text : si’elle est null c’est que l’utilisateur n’a pas respecté la condition (ici 5 caractères).

On peut facilement imaginer une mise en couleur de la PostalBox dans le cas où le code postal correspond à un pattern plus sophistiqué.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *