[WPF] Binding sur les paramètres d’application à l’aide d’une Markup extension

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...

Voilà, c’est fait, j’ai créé mon blog sur .NET… j’ai mis le temps, mais j’ai fini par y venir 😉

Je me présente rapidement : Thomas Levesque, 27 ans, ingénieur de formation. Je suis passionné depuis toujours par l’informatique, et plus particulièrement par la technologie .NET, que je suis de très près depuis ses débuts. Comme je suis du genre curieux, je passe pas mal de temps à fouiner dans les docs MSDN et sur le net pour m’auto-former sur les dernières nouveautés du framework. Aujourd’hui, je travaille comme développeur C# pour une PME en région parisienne.

Mais assez parlé de moi, venons-en au sujet qui nous intéresse : le binding sur les paramètres d’application en WPF.

Pour l’utilisateur d’une application, il est plus confortable de ne pas avoir à redéfinir sans arrêt ses préférences (dimensions de la fenêtre, activation de telle ou telle option…) : d’où l’utilité des paramètres d’application, introduits dans la version 2.0 du framework. Pour le développeur, c’est un peu pénible… même avec la classe Settings définie par Visual Studio, il reste encore pas mal de code à écrire pour lire et appliquer les paramètres au démarrage de l’appli, puis les enregistrer avant de quitter.

Dans Windows Forms, on pouvait définir des bindings entre les propriétés des contrôles et les paramètres d’application, mais ça restait peu pratique, et finalement assez peu utilisé (du moins c’est l’impression que j’en ai…).

Avec WPF, ça devient beaucoup plus intéressant… bien qu’il n’y ait pas de documentation “officielle” sur cette pratique, il est tout à fait possible de définir dans le code XAML des bindings sur les paramètres d’application. Par exemple, pour binder les dimensions de la fenêtre sur des paramètres, la méthode qui est présentée sur de nombreux blogs est la suivante :

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:p="clr-namespace:WpfApplication1.Properties"
        Title="Window1"
        Height="{Binding Source={x:Static p:Settings.Default}, Path=Height, Mode=TwoWay}"
        Width="{Binding Source={x:Static p:Settings.Default}, Path=Width, Mode=TwoWay}"
        Left="{Binding Source={x:Static p:Settings.Default}, Path=Left, Mode=TwoWay}"
        Top="{Binding Source={x:Static p:Settings.Default}, Path=Top, Mode=TwoWay}">

(Dans cet exemple, Height, Width, Top et Left sont des paramètres de l’application)

Cette méthode a l’avantage de fonctionner, mais franchement, est-ce que vous vous voyez écrire ça des dizaines de fois, pour chaque paramètre de l’application ? C’est long à écrire, peu intuitif, répétitif, et ça rend le code globalement peu lisible…

Loin de moi l’idée de dénigrer ceux qui ont eu cette idée, bien sûr… mais il est très facile d’améliorer cette méthode, en créant notre propre « markup extension ». Nous allons donc définir une classe qui va hériter de Binding, et servir spécifiquement à lier des propriétés à des paramètres de l’application.

Une « markup extension » (qu’on pourrait traduire approximativement par « balise d’extension ») est un objet qu’on peut utiliser dans le code XAML pour récupérer une valeur. On en utilise en permanence en WPF : Binding, StaticResource, DynamicResource sont des exemples de markup extensions.

On peut facilement définir sa propre markup extension en créant une classe dérivée de MarkupExtension, qui doit implémenter une méthode ProvideValue. En l’occurrence, l’essentiel de ce qu’on veut faire est déjà implémenté dans la classe Binding (qui hérite indirectement de MarkupExtension). Nous allons donc hériter directement de Binding, et simplement initialiser les propriétés qui nous intéressent pour se binder sur les paramètres :

using System.Windows.Data;

namespace WpfApplication1
{
    public class SettingBindingExtension : Binding
    {
        public SettingBindingExtension()
        {
            Initialize();
        }

        public SettingBindingExtension(string path)
            :base(path)
        {
            Initialize();
        }

        private void Initialize()
        {
            this.Source = WpfApplication1.Properties.Settings.Default;
            this.Mode = BindingMode.TwoWay;
        }
    }
}

Notez le suffixe « Extension » à la fin du nom de la classe : par convention, la plupart des markup extensions ont ce suffixe (Binding est une exception qui confirme la règle…). Ce suffixe pourra être omis quand on utilisera la classe en XAML (un peu comme les attributs, dont on omet le suffixe « Attribute » ).

Dans cette classe, on a défini 2 constructeurs, qui correspondent à ceux de Binding. On ne redéfinit pas la méthode ProvideValue, car celle de la classe Binding nous convient parfaitement (et d’ailleurs elle est marquée comme finale (sealed), si bien qu’on ne pourrait pas la redéfinir…). Le code « intéressant », si j’ose dire, est dans la méthode Initialize. On y définit la propriété Source comme étant les paramètres de notre appli, de façon à ce que le Path indiqué pour le binding renvoie le paramètre qui nous intéresse, et Mode = TwoWay, de façon à ce que les paramètres soient automatiquement mis à jour à partir de l’interface. L’intérêt étant de ne pas avoir à redéfinir ces propriétés à chaque fois…

Pour utiliser cette classe, c’est tout simple ! Reprenons l’exemple précédent, en remplaçant les Binding par notre extension SettingBinding :

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:WpfApplication1"
        Title="Window1"
        Height="{my:SettingBinding Height}"
        Width="{my:SettingBinding Width}"
        Left="{my:SettingBinding Left}"
        Top="{my:SettingBinding Top}">

C’est tout de même plus lisible et plus facile à utiliser…

Ah, et bien sûr, pour que ça marche, on n’oublie pas d’enregistrer les paramètres dans l’évènement Exit de l’application…

        private void Application_Exit(object sender, ExitEventArgs e)
        {
            WpfApplication1.Properties.Settings.Default.Save();
        }

Et voilà, les dimensions de la fenêtre seront enregistrées et restaurées à chaque lancement de l’application, sans qu’on ait rien de plus à coder !

Télécharger les sources

Mise à jour : Si vous voulez en savoir plus sur les markup extensions, je vous invite à lire le tutoriel que j’ai écrit suite à ce billet : Les markup extensions en WPF

8 Comments

  1. Olivier says:

    la class binding surchage le ProvideValue en “sealed” et nous empeche de la reecrire.
    Y a t”il un moyen de recuperer la valeur d”un nouveau parametre comme on le fait habituellement dans le ProvideValue des markup classiques?

  2. Bonjour,
    Je ne comprends pas bien ce que tu cherches à faire… pour récupérer les paramètres d”application il suffit de définir la source du binding : pas la peine de redéfinir ProvideValue. Si tu veux refaire un traitement sur la valeur fournie par Binding.ProvideValue, tu peux ajouter un Converter. Si tu veux définir toi-même ProvideValue… n”hérite pas de Binding 😉
    Explique ce que tu veux faire, il y a peut-être une autre approche…

  3. letreste bruno says:

    j”aimerais assez pouvoir sur ton projet mettre en œuvre un contrôle sur par exemple le fait d”avoir la fenêtre dans l”écran, par exemple vérifier que si le path est width la valeur a renvoyer doit obligatoirement être >= 0
    par contre comme je ne maitrise pas vraiment la dérivation de binding, ou faudrait-il mettre la méthode qui permettrait le controle ?
    évidement on pourrait mettre un converter a chaque fois pour le contrôle … mais bon (ca me parrait moins sexy lol)

  4. Ben le problème, c”est que la classe Binding “verrouille” pas mal de choses : la plupart des méthodes et propriétés ne sont pas virtuelles, la méthode ProvideValue est sealed… donc on ne peut pas vraiment redéfinir son fonctionnement. Donc à part les Converters, je ne vois pas trop…
    Ah si, une solution peut-être : créer ta propre markup extension, qui n”hérite pas de Binding… mais ça demande un peu plus de travail, vu qu”il faut gérer soi-même la mise à jour des settings. Si tu veux faire ça, tu trouveras peut-être de l”inspiration dans mon article (lien à la fin du post)

  5. letreste bruno says:

    oui j”avais naïvement tente un new sur le providevalue ehehehehe mais evidement va passe pas dedans

  6. letreste bruno says:

    voila la solution qui me parait etre la meilleurs et la plus simple pour ce cas (c”est pas complet mais ca donne une idee)

    public class SettingBindingExtension : Binding
    {
    public SettingBindingExtension()
    {
    Initialize(“”);
    }

    public SettingBindingExtension(string path)
    : base(path)
    {
    Initialize(path);
    }

    private void Initialize(string path)
    {
    this.Source = WpfApplication1.Properties.Settings.Default;
    this.Mode = BindingMode.TwoWay;
    this.Converter = new InternalConverter();
    this.ConverterParameter = path;
    }

    private class InternalConverter : IValueConverter
    {
    #region IValueConverter Membres

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
    double v = (double)value;
    switch ((parameter as string).ToLower())
    {
    case “left”:
    if (v < 0) v = 0;
    break;

    }
    return v;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
    return value;
    }

    #endregion
    }
    }

  7. Effectivement c”est une possibilité… mais je pense pas que ce soit une bonne idée de mettre le converter en dur dans la markup extension, parce que c”est spécifique à certains paramètres… il vaudrait mieux le déclarer en XAML

  8. letreste bruno says:

    d”autant que si on veut pinailler il reste le problème de la taille par rapport aux positions pour éviter le débordement de la fenêtre (par exemple width+left > screen.width)

Leave a comment

css.php