Binding d’une énumération avec WPF

A l’aide du binding d’un objet sous WPF,  il est possible de laisser WPF se charger de l’ensemble des mises à jour entre l’interface et l’objet ce qui permet d’accélérer (a mon sens) le temps de développement de l’interface.

Pour ce qui est du binding d’une chaine vers un textbox, un boolean vers une checkbox, pas de problème. Par contre pour ce qui est des énumérations c’est moins simple … Voici quelques bouts de solutions pour traiter ce problème et pouvoir charger une ComboBox ou listbox avec notre énumération.

1- Utiliser le GetValues de l’enum

Pour commencer il faut ajouter deux namespaces à notre objet Window dans l’XAML, le namespace system et celui de notre assembly :

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:System="clr-namespace:System;assembly=mscorlib"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="Window1" 
    mc:Ignorable="d" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    d:DataContext="{d:DesignInstance Type=local:MonObjet}" 
    Height="300" Width="567">

Ensuite nous allons ajouter une ressources qui contiendra la liste des choix de l’énumération :

<Window.Resources>
        <ObjectDataProvider x:Key="dataFromEnum"
       MethodName="GetValues" ObjectType="{x:Type System:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:MonTypeEnum"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
</Window.Resources>

Il reste enfin a ajouter cette ressources comme ItemSource de la ComboBox :

<ComboBox Name="ComboBox1"
     ItemsSource="{Binding Source={StaticResource dataFromEnum}}" 
     SelectedItem="{Binding Path=MaVariable}" >

2 – Utiliser une ListBox en liste de radiobutton

La c’est un complément de la solution précédente, si comme moi vous préférez utiliser des radiobutton il est possible de modifier le style d’une listbox en liste de radiobutton. Pour cela rajoutez dans les ressources le style suivant :

<Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}">
            <Setter Property="BorderBrush" Value="{x:Null}" />
            <Setter Property="BorderThickness" Value="0" />
            <Setter Property="ItemContainerStyle">
                <Setter.Value>
                    <Style TargetType="{x:Type ListBoxItem}" >
                        <Setter Property="Margin" Value="2" />
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                    <Border Background="Transparent">
                                        <RadioButton Focusable="False"
                        IsHitTestVisible="False"
                        IsChecked="{TemplateBinding IsSelected}">
                                            <ContentPresenter />
                                        </RadioButton>
                                    </Border>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </Setter.Value>
            </Setter>
        </Style>

Il reste enfin a ajouter ce style à la listbox et vous obtiendrez une liste de radiobutton :

<ListBox Name="ListBox1" Style="{StaticResource RadioButtonList}"
     ItemsSource="{Binding Source={StaticResource dataFromEnum}}" 
     SelectedItem="{Binding Path=MaVariable}" >

 3 – Utiliser une classe de convertion

Cette solution provient du site suivant :http://www.karmian.org/net-tips-tricks-examples-articles/enum-description-typeconverter.

Le principe est d’utiliser une classe pour convertir l’affichage des choix de l’énumération. Ajouter ce code a votre solution :

Public Class EnumDescriptionTypeConverter
    Inherits EnumConverter
    Public Sub New(type As Type)
        MyBase.New(type)
    End Sub

    Public Overrides Function CanConvertFrom(context As ITypeDescriptorContext, sourceType As Type) As Boolean
        Return sourceType Is GetType(String) OrElse TypeDescriptor.GetConverter(GetType([Enum])).CanConvertFrom(context, sourceType)
    End Function

    Public Overrides Function ConvertFrom(context As ITypeDescriptorContext, culture As System.Globalization.CultureInfo, value As Object) As Object
        If TypeOf value Is String Then
            Return GetEnumValue(EnumType, DirectCast(value, String))
        End If
        If TypeOf value Is [Enum] Then
            Return GetEnumDescription(DirectCast(value, [Enum]))
        End If
        Return MyBase.ConvertFrom(context, culture, value)
    End Function

    Public Overrides Function ConvertTo(context As ITypeDescriptorContext, culture As System.Globalization.CultureInfo, value As Object, destinationType As Type) As Object
        Return If(TypeOf value Is [Enum] AndAlso destinationType Is GetType(String), GetEnumDescription(DirectCast(value, [Enum])), (If(TypeOf value Is String AndAlso destinationType Is GetType(String), GetEnumDescription(EnumType, DirectCast(value, String)), MyBase.ConvertTo(context, culture, value, destinationType))))
    End Function

    Public Shared Function GetEnumDescription(value As [Enum]) As String
        Dim fieldInfo = value.[GetType]().GetField(value.ToString())
        Dim attributes = DirectCast(fieldInfo.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
        Return If((attributes.Length > 0), attributes(0).Description, value.ToString())
    End Function

    Public Shared Function GetEnumDescription(value As Type, name As String) As String
        Dim fieldInfo = value.GetField(name)
        Dim attributes = DirectCast(fieldInfo.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
        Return If((attributes.Length > 0), attributes(0).Description, name)
    End Function

    Public Shared Function GetEnumValue(value As Type, description As String) As Object
        Dim fields = value.GetFields()
        For Each fieldInfo As Reflection.FieldInfo In fields
            Dim attributes = DirectCast(fieldInfo.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
            If attributes.Length > 0 AndAlso attributes(0).Description = description Then
                Return fieldInfo.GetValue(fieldInfo.Name)
            End If
            If fieldInfo.Name = description Then
                Return fieldInfo.GetValue(fieldInfo.Name)
            End If
        Next
        Return description
    End Function
End Class

Vous pouvez ensuite « décrire » votre énumération comme ceci :

<TypeConverter(GetType(EnumDescriptionTypeConverter))> _
Public Enum MonEnum
    <Description("Choix 1")> test1 = 1
    <Description("Choix 2")> test2 = 2
    <Description("Choix 3")> test4 = 3
    test5 = 4
End Enum

Le texte situé dans la description sera alors utilisé pour l’affichage des choix dans l’interface WPF.

Typage du datacontext – binding avec WPF directement dans le XAML

Pour pouvoir typer le binding d’une fenêtre en WPF il suffit d’utiliser une simple astuce a défaut de pouvoir le faire plus simplement dans visual studio. Cela fonctionne pour moi dans visual studio 2010, avec un projet en vb.Net sous le framework 4.

Le principe est simple : nous allons modifier la définition XAML de la fenêtre pour lui ajouter des propriétés et surtout définir notre datacontext.

En premier, vous devez ajouter le namespace contenant l’objet utilisé pour la liaison de données (dans l’exemple ce sera le namespace du projet actuel) :

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="Window1"
    Height="300" Width="567">

Ensuite vous allez cliquer sur votre « objet » fenêtre dans le designer WPF et cliquez sur le petit carré en bas à droite de la fenêtre :

WPF

Vous devez cliquer deux fois pour revenir en mode normal (taille fixe). En effectuant cette opération vous allez ajouter à votre code XAML deux namespaces XML :

mc:Ignorable="d" 
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

Vous devez enfin ajouter la description du datacontext avec votre objet :

d:DataContext="{d:DesignInstance Type=local:MonObjet}"

Ce qui nous donne au final dans le XAML :

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="Window1" 
    mc:Ignorable="d" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    d:DataContext="{d:DesignInstance Type=local:MonObjet}"
    Height="300" Width="567">

A partir de maintenant il est possible depuis l’éditeur WPF de parcourir les propriétés du datacontext et donc de notre objet pour sélectionner les liaisons de données.

Voici un exemple avec la propriété Text d’un TextBox ayant une liaison de données vers le datacontext, qui est du type « Personne » qui sert d’exemple :

WPF2

Qui se traduit dans le XAML par :

Text="{Binding Path=Nom}"

Mais c’est plus sympa de le faire visuellement, surtout si on a beaucoup de propriété…