[WPF] Développer un contrôle permettant l'affichage de graphiques (histograme, etc....) avec WPF
Au travers de ce post, je vais m'attacher à vous expliquer comment utiliser WPF (Windows Presentation Foundation) pour développer un contrôle permettant d'afficher des graphiques 
Avant de commencer, il est important de comprendre une chose: un graphique, ce n'est qu'un ensemble de valeur. Après, seule la manière dont ces valeurs sont affichées est susceptible de changer.
Pour représenter un contrôle affichant plusieurs valeurs, avec WPF, vous avez à votre disposition les ItemsControl. Nos allons donc créer un petit contrôle qui hérite d'ItemControl:
public class Graph : ItemsControl
Dès lors, vous n'avez plus grand chose à faire, si ce n'est indiquer la source des données de notre contrôle puis modifier son ItemTemplate afin d'indiquer comment afficher les différents éléments.
Voici un exemple possible d'ItemTemplate, permettant d'afficher un histograme:
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Rectangle VerticalAlignment="Bottom" Width="25" Height="{Binding}" Fill="Red" Margin="5,0,5,0"/>
</DataTemplate>
</Setter.Value>
</Setter>
Pour utiliser ce contrôle, là encore, ce n'est pas compliqué:
<TabItem Header="Graph">
<TabItem.DataContext>
<collections:ArrayList>
<sys:Int32>25</sys:Int32>
<sys:Int32>99</sys:Int32>
<sys:Int32>56</sys:Int32>
</collections:ArrayList>
</TabItem.DataContext>
<graph:Graph ItemsSource="{Binding}" />
</TabItem>
Comme vous pouvez le voir, on déclare juste une ArrayList comme étant source des données de notre graph, et à l'affichage, l'ItemTemplate que nous avons définit est bien pris en compte:
Bon, c'est sympa tout ca mais on peut pas aller plus loin ? En fait si, on peut faire énormément de choses avec WPF !
Notre contrôle de graph est bien sympathique mais il serait tout de même mieux avec une légende. Nous allons donc implémenter cette légende et plutôt que de fixer une valeur en dur dans le code, nous allons laisser le choix à l'utilisateur....
Pour cela, on va utiliser une DependencyProperty. Pourquoi utiliser une DP et non pas une simple propriété ? En effet, nous n'allons pas binder, animer, transformer, etc... cette propriété (ce que permettent de faire les DP, contrairement aux propriétés simples). Tout simplement parce que l'on ne sait jamais. Qui vous dit que demain, vous n'aurez pas besoin de binder cette propriété à un champ d'une table d'une base de données, etc... 
public string Legend
{
get { return (string)GetValue(LegendProperty); }
set { SetValue(LegendProperty, value); }
}
// Using a DependencyProperty as the backing store for Legend. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LegendProperty =
DependencyProperty.Register("Legend", typeof(string), typeof(Graph), new FrameworkPropertyMetadata(string.Empty));
Une fois cette partie terminée, il nous faut modifier le template de notre contrôle, pour lui dire d'afficher cette légende:
<ControlTemplate>
<Border SnapsToDevicePixels="True" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=Legend, RelativeSource={RelativeSource AncestorType={x:Type graph:Graph}}}" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0,0,0,20" />
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</StackPanel>
</Border>
</ControlTemplate>
Beaucoup de code (peut-être compliqué pour certains) pour pas grand chose: on indique que le template de notre Graph sera en fait composé d'une Bordure qui contiendra un StackPanel qui lui-même contiendra une TextBlock (pour la légende) et un ItemPresenter (pour les données).
Si l'on regarde le résultat, miracle: tout fonctionne 
Mais ce n'est pas tout ! Un graphique de ce type possède une particularité: les valeurs peuvent-être afficher horizontalement (comme c'est le cas ici) ou bien verticalement. Et dans le 2ème cas, ce n'est pas sur la hauteur des éléments que nous allons devoir binder, mais bien leur largeur. Il va donc nous falloir une propriété qui nous permet de spécifier l'orientation de notre contrôle et suivant la valeur de cette propriété, on utilisera le Template adéquat.
Commençons par la propriété utilisée pour indiquer l'orientation:
public Orientation GraphOrientation
{
get { return (Orientation)GetValue(GraphOrientationProperty); }
set { SetValue(GraphOrientationProperty, value); }
}
// Using a DependencyProperty as the backing store for GraphOrientation. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GraphOrientationProperty =
DependencyProperty.Register("GraphOrientation", typeof(Orientation), typeof(Graph), new FrameworkPropertyMetadata(Orientation.Horizontal));
Maintenant, comment faire pour indiquer à notre application d'utiliser tel ou tel template, en fonction de la valeur de notre propriété ? Tout simplement en utilisant les Triggers:
<!-- Orientation Vertical -->
<Trigger Property="GraphOrientation" Value="Vertical">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Rectangle HorizontalAlignment="Left" Width="{Binding}" Height="25" Fill="Red" Margin="0,5,0,5"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Trigger>
<!-- Orientation Horizontal -->
<Trigger Property="GraphOrientation" Value="Horizontal">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Rectangle VerticalAlignment="Bottom" Width="25" Height="{Binding}" Fill="Red" Margin="5,0,5,0"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Trigger>
Et le tour est joué: votre contrôle affiche à présent ses éléments de la façon dont vous l'indiquez (ou bien il utilise la valeur par défaut, Orientation.Horizontal):
<graph:Graph ItemsSource="{Binding}" Legend="Various numbers" GraphOrientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" />
Vous l'aurez compris, si vous souhaitez transformer cet histograme en camembert ou autre, vous n'aurez qu'à modifier le Template et l'ItemTemplate 
Bon code à vous tous 
A+
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 :