Tag Archives: binding

Détecter les changements d’une propriété de dépendance dans WinRT

Aujourd’hui j’aimerais partager une astuce que j’ai utilisée en développant ma première application Windows Store. Je suis complètement nouveau sur cette technologie et c’est mon premier billet à ce sujet, donc j’espère que je ne vais pas trop me ridiculiser…

Il est souvent utile d’être notifié quand la valeur d’une propriété de dépendance change ; beaucoup de contrôles exposent des évènements à cet effet, mais ce n’est pas toujours le cas. Par exemple, récemment j’essayais de détecter les changements de la propriété Content d’un ContentControl. En WPF, j’aurais utilisé la classe DependencyPropertyDescriptor, mais elle n’est pas disponible dans WinRT.

Heureusement, il y a un mécanisme qui existe sur toutes les plateformes XAML, et qui peut résoudre ce problème: le binding. La solution est donc simplement de créer une classe avec un propriété “bidon” qui est liée à la propriété qu’on souhaite observer, et d’appeler un handler quand la valeur de cette propriété bidon change. Pour rendre ça un peu plus propre et masquer l’implémentation réelle, j’ai emballé ça sous forme d’une méthode d’extension qui renvoie un IDisposable:

    public static class DependencyObjectExtensions
    {
        public static IDisposable WatchProperty(this DependencyObject target,
                                                string propertyPath,
                                                DependencyPropertyChangedEventHandler handler)
        {
            return new DependencyPropertyWatcher(target, propertyPath, handler);
        }

        class DependencyPropertyWatcher : DependencyObject, IDisposable
        {
            private DependencyPropertyChangedEventHandler _handler;

            public DependencyPropertyWatcher(DependencyObject target,
                                             string propertyPath,
                                             DependencyPropertyChangedEventHandler handler)
            {
                if (target == null) throw new ArgumentNullException("target");
                if (propertyPath == null) throw new ArgumentNullException("propertyPath");
                if (handler == null) throw new ArgumentNullException("handler");

                _handler = handler;

                var binding = new Binding
                {
                    Source = target,
                    Path = new PropertyPath(propertyPath),
                    Mode = BindingMode.OneWay
                };
                BindingOperations.SetBinding(this, ValueProperty, binding);
            }

            private static readonly DependencyProperty ValueProperty =
                DependencyProperty.Register(
                    "Value",
                    typeof(object),
                    typeof(DependencyPropertyWatcher),
                    new PropertyMetadata(null, ValuePropertyChanged));

            private static void ValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var watcher = d as DependencyPropertyWatcher;
                if (watcher == null)
                    return;

                watcher.OnValueChanged(e);
            }

            private void OnValueChanged(DependencyPropertyChangedEventArgs e)
            {
                var handler = _handler;
                if (handler != null)
                    handler(this, e);
            }

            public void Dispose()
            {
                _handler = null;
                // There is no ClearBinding method, so set a dummy binding instead
                BindingOperations.SetBinding(this, ValueProperty, new Binding());
            }
        }
    }

On peut l’utiliser comme ceci:

// Abonnement
watcher = myControl.WatchProperty("Content", myControl_ContentChanged);

// Désabonnement
watcher.Dispose();

J’espère que vous trouverez cela utile!

[WPF] Comment faire un binding dans les cas où on n’hérite pas du DataContext

La propriété DataContext de WPF est extrêmement pratique, car elle est automatiquement héritée par tous les enfants de l’élément où elle est définie ; il n’est donc pas nécessaire de la redéfinir pour chaque élément qu’on veut lier aux données. Cependant, il arrive que le DataContext ne soit pas accessible pour certains éléments : c’est le cas des éléments qui n’appartiennent pas à l’arbre visuel ni à l’arbre logique. Il devient alors très difficile de définir une propriété ce ces éléments par un binding…

Prenons un exemple simple : on veut afficher une liste de produits dans un DataGrid. Dans la grille, on veut pouvoir afficher où masquer la colonne du prix, en fonction d’une propriété ShowPrice exposée par le ViewModel. L’approche évidente consiste à binder la propriété Visibility de la colonne à la propriété ShowPrice :

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding ShowPrice,
                        Converter={StaticResource visibilityConverter}}"/>

Malheureusement, changer la valeur de la propriété ShowPrice n’a aucun effet, et la colonne reste toujours affichée… pourquoi ? Si on regarde la fenêtre de sortie de Visual Studio, on remarque la ligne suivante :

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ShowPrice; DataItem=null; target element is ‘DataGridTextColumn’ (HashCode=32685253); target property is ‘Visibility’ (type ‘Visibility’)

Derrière cet obscur charabia se cache une explication toute simple : WPF ne sait pas quel FrameworkElement utiliser pour récupérer le DataContext, car la colonne n’appartient pas à l’arbre visuel ni à l’arbre logique du DataGrid.

On peut toujours essayer de “triturer” le binding pour obtenir le résultat voulu, par exemple en essayant de binder par rapport au DataGrid lui-même :

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding DataContext.ShowPrice,
                        Converter={StaticResource visibilityConverter},
                        RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>

Ou encore, en ajoutant une CheckBox bindée sur ShowPrice et en essayant de binder la visibilité de la colonne sur la propriété IsChecked, en spécifiant le nom de l’élément :

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding IsChecked,
                        Converter={StaticResource visibilityConverter},
                        ElementName=chkShowPrice}"/>

Mais rien à faire, on obtient toujours le même résultat…

A ce stade, il semble que la seule approche qui pourrait marcher est de passer par le code-behind, ce qu’on préfère généralement éviter quand on suit le pattern MVVM… mais ce serait dommage d’abandonner aussi vite 😉

La solution est en fait assez simple, et se base sur la classe Freezable. La vocation première de cette classe est de définir des objets qui ont un état modifiable et un état non modifiable. Mais en l’occurrence, la caractéristique qui nous intéresse est qu’un objet qui hérite de Freezable peut hériter du DataContext, bien qu’il ne s’agisse pas d’un élément visuel. Je ne connais pas le mécanisme exact qui permet d’obtenir ce comportement, mais toujours est-il que cela va nous permettre d’arriver au résultat voulu…

L’idée est de créer une classe, que j’ai appelée BindingProxy, qui hérite de Freezable et dans laquelle on va déclarer une dependency property Data :

    public class BindingProxy : Freezable
    {
        #region Overrides of Freezable

        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }

        #endregion

        public object Data
        {
            get { return (object)GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }

On va ensuite déclarer une instance de cette classe dans les ressources du DataGrid, et binder la propriété Data sur le DataContext courant :

<DataGrid.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

Il suffit ensuite de spécifier que la source de notre binding est cet objet BindingProxy, facilement accessible puisqu’il est déclaré comme ressource :

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding Data.ShowPrice,
                        Converter={StaticResource visibilityConverter},
                        Source={StaticResource proxy}}"/>

Remarquez qu’on a aussi préfixé le chemin du binding par “Data”, puisque le chemin est maintenant relatif à l’objet BindingProxy.

Le binding fonctionne maintenant comme prévu, moyennant une solution relativement simple à mettre en oeuvre…

[VS 2010] Support du binding dans les InputBindings

LA fonctionnalité qui manquait à WPF !

La beta 2 de Visual Studio 2010 est là depuis quelques jours, et apporte à WPF une nouveauté que j’attendais depuis longtemps : le support du binding dans les InputBindings.

Pour rappel, le problème de la version précédente était que la propriété Command de la classe InputBinding n’était pas une DependencyProperty, on ne pouvait donc pas la définir par un binding. D’ailleurs, les InputBindings n’héritaient pas du DataContext, ce qui compliquait beaucoup les implémentations alternatives de cette fonctionnalité…

Jusqu’ici, pour lier la commande d’un KeyBinding ou MouseBinding à une propriété du DataContext, il fallait donc passer par des détours pas forcément très élégants… J’avais fini par trouver une solution acceptable, détaillée dans ce post, mais qui me laissait assez insatisfait (utilisation de la réflexion sur des membres privés, pas mal de limitations…).

J’ai découvert plus récemment le MVVM toolkit, qui propose une approche un peu plus propre : une classe CommandReference, héritée de Freezable, qui permet de mettre dans les ressources de la page ou du contrôle une référence à la commande, qu’on peut ensuite utiliser avec StaticResource. Plus propre, mais ça restait peu intuitif…

WPF 4.0 résout le problème une bonne fois pour toutes : la classe InputBinding hérite maintenant de Freezable, ce qui lui permet d’hériter du DataContext parent. De plus les propriétés Command, CommandParameter et CommandTarget sont maintenant des DependencyProperty. On peut donc laisser tomber tous les bricolages qu’il fallait utiliser jusqu’à maintenant, et aller droit au but :

    <Window.InputBindings>
        <KeyBinding Key="F5" Command="{Binding RefreshCommand}" />
    </Window.InputBindings>

Voilà qui devrait faciliter un peu le développement d’applications MVVM !

Help 3

Je change un peu de sujet pour vous parler du nouveau système de documentation offline de Visual Studio 2010, appelé “Help 3”. Il se présente comme une application web installée localement, les pages s’affichent donc dans le navigateur. Globalement, c’est beaucoup mieux… largement plus léger et plus réactif que le vieux Document Explorer livré avec les versions précédentes de Visual Studio.

En revanche, une fonction qui m’était absolument indispensable a disparu : l’index ! Il ne reste que la vue en arborescence et la recherche. Plus de trace de l’index que j’utilisais en permanence pour accéder directement à une classe ou un membre, sans forcément connaître son namespace. En plus, les résultats de la recherche ne montre pas clairement le namespace : par exemple, si vous tapez “button class” dans la recherche, pas moyen de voir la différence entre System.Windows.Forms.Button, System.Windows.Controls.Button et System.Web.UI.WebControls ! Avant, le volet “Résultats de l’index” affichait cette information clairement.

Mon sentiment sur ce nouveau système d’aide est donc un peu mitigé… il va falloir que je change ma façon d’utiliser la doc. Mais à part ce détail agaçant, c’est objectivement un gros progrès !

[WPF] Binding sur une collection asynchrone

Comme je l’avais évoqué dans mon précédent post, on ne peut pas ajouter des éléments à une ObservableCollection à partir d’un autre thread si une vue est bindée sur la collection : cela provoque une NotSupportedException. Prenons l’exemple d’une ListBox bindée sur une collection de chaines de caractères appartenant au ViewModel :

        private ObservableCollection<string> _strings = new ObservableCollection<string>();
        public ObservableCollection<string> Strings
        {
            get { return _strings; }
            set
            {
                _strings = value;
                OnPropertyChanged("Strings");
            }
        }
<ListBox ItemsSource="{Binding Strings}"/>

Si on ajoute des éléments à cette collection hors du thread principal, on obtient l’exception citée plus haut. Une solution est de créer une nouvelle liste, puis de l’affecter à la propriété Strings quand elle est remplie, mais dans ce cas l’interface graphique ne reflète pas la progression : les éléments de la liste apparaissent tous à la fois quand la liste est remplie, et non au fur et à mesure que les éléments sont ajoutés. Si la liste correspond aux résultats d’une recherche, par exemple, ça peut être assez gênant car l’utilisateur s’attend à voir les résultats apparaître au fur et à mesure qu’ils sont trouvés (comme dans la recherche Windows).

Un moyen simple d’obtenir le comportement voulu est de créer une classe héritée de ObservableCollection qui déclenche les évènements CollectionChanged et PropertyChanged sur le thread principal au lieu du thread courant. La classe AsyncOperation se prête parfaitement à cet objectif : elle permet de “poster” un évènement sur le thread qui l’a créée. Elle est notamment utilisée par le composant BackgroundWorker et de nombreuses méthodes asynchrones du framework (PictureBox.LoadAsync, WebClient.DownloadAsync, etc…).

Voici donc le code d’une collection AsyncObservableCollection qui peut être modifiée à partir de n’importe quel thread tout en notifiant l’interface graphique lors d’une modification :

    public class AsyncObservableCollection<T> : ObservableCollection<T>
    {
        private AsyncOperation asyncOp = null;

        public AsyncObservableCollection()
        {
            CreateAsyncOp();
        }

        public AsyncObservableCollection(IEnumerable<T> list)
            : base(list)
        {
            CreateAsyncOp();
        }

        private void CreateAsyncOp()
        {
            // Create the AsyncOperation to post events on the creator thread
            asyncOp = AsyncOperationManager.CreateOperation(null);
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            // Post the CollectionChanged event on the creator thread
            asyncOp.Post(RaiseCollectionChanged, e);
        }

        private void RaiseCollectionChanged(object param)
        {
            // We are in the creator thread, call the base implementation directly
           base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
        }

        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            // Post the PropertyChanged event on the creator thread
            asyncOp.Post(RaisePropertyChanged, e);
        }

        private void RaisePropertyChanged(object param)
        {
            // We are in the creator thread, call the base implementation directly
            base.OnPropertyChanged((PropertyChangedEventArgs)param);
        }
    }

La seule contrainte est de créer les instances de cette collection sur le thread de l’interface graphique, afin que les évènements soient bien déclenchés sur ce thread.

Si on reprend le code de l’exemple précédent, la seule chose à changer pour pouvoir modifier la collection à partir d’un autre thread est l’instantiation de la collection dans le ViewModel :

private ObservableCollection<string> _strings = new AsyncObservableCollection<string>();

La ListBox peut maintenant refléter en temps réel les changements intervenus dans la collection.

Enjoy 😉

Mise à jour : Je viens de remarquer un bug dans mon implémentation : dans certains cas le fait de passer par un Post pour lever l’évènement alors que la collection est modifiée à partir du thread principal peut produire un comportement inattendu. Dans ce cas il faut évidemment lever l’évènement directement, en vérifiant que le SynchronizationContext courant est le même que celui dans lequel a été créée la collection. Et puisqu’on en est à se préoccuper du SynchronizationContext, autant l’utiliser directement et se passer de l’AsyncOperation, qui finalement n’apporte rien. Voici donc la nouvelle implémentation :

    public class AsyncObservableCollection<T> : ObservableCollection<T>
    {
        private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;

        public AsyncObservableCollection()
        {
        }

        public AsyncObservableCollection(IEnumerable<T> list)
            : base(list)
        {
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (SynchronizationContext.Current == _synchronizationContext)
            {
                // Raise the CollectionChanged event on the current thread
                RaiseCollectionChanged(e);
            }
            else
            {
                // Raise the CollectionChanged event on the creator thread
                _synchronizationContext.Send(RaiseCollectionChanged, e);
            }
        }

        private void RaiseCollectionChanged(object param)
        {
            // We are in the creator thread, call the base implementation directly
            base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
        }

        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (SynchronizationContext.Current == _synchronizationContext)
            {
                // Raise the PropertyChanged event on the current thread
                RaisePropertyChanged(e);
            }
            else
            {
                // Raise the PropertyChanged event on the creator thread
                _synchronizationContext.Send(RaisePropertyChanged, e);
            }
        }

        private void RaisePropertyChanged(object param)
        {
            // We are in the creator thread, call the base implementation directly
            base.OnPropertyChanged((PropertyChangedEventArgs)param);
        }
    }

Mise à jour : modifié le code pour utiliser Send plutôt que Post. L’utilisation de Post faisait que l’évènement était déclenché de façon asynchrone sur le thread UI, ce qui pouvait causer une race condition si la collection était modifiée à nouveau avant que l’évènement ne soit géré.

[WPF] Binding asynchrone sur une propriété du ViewModel

Mise à jour : Comme l’a très justement indiqué Jérémy en commentaire, la propriété IsAsync du Binding permet de faire à peu près la même chose beaucoup plus simplement… Bien que ma méthode puisse servir pour certains besoins spécifiques, dans la plupart des cas la propriété IsAsync est probablement le meilleur choix ! Je laisse le billet malgré tout, ne serait-ce que pour la classe SwitchBinding qui me semble assez utile…

J’ai eu récemment besoin, dans une application basée sur le pattern MVVM, d’afficher une propriété dont la valeur était assez longue à obtenir (récupérer par une requête HTTP). Au départ, j’ai simplement implémenté la propriété en suivant le principe du lazy loading : le binding sur cette propriété provoquait donc l’obtention de la valeur par une requête HTTP. Le résultat, prévisible, était un gel de l’interface pendant la récupération de la valeur. La solution classique pour ce genre de problème est de récupérer la valeur sur un autre thread, et d’affecter le résultat au contrôle qui doit l’afficher… sauf qu’en MVVM on n’a pas accès à ce contrôle. Une autre approche, plus adaptée, est d’affecter la valeur à la propriété du ViewModel, ce qui déclenche l’évènement PropertyChanged et rafraichit la vue.

J’ai essayé pas mal de choses avant d’arriver à une solution avec le moins possible de code de “plomberie”, je vais donc vous la faire partager. Voilà le code de la propriété :

        private bool _retrievingValue = false;

        private object _value;
        public object Value
        {
            get
            {
                if (_value == null && !_retrievingValue)
                {
                    _retrievingValue = true;
                    ThreadPool.QueueUserWorkItem(
                        (state) =>
                        {
                            this.Value = _model.RetrieveValue(); // Very long operation...
                            _retrievingValue = false;
                        });
                }
                return _value;
            }
            set
            {
                _value = value;
                OnPropertyChanged("Value");
            }
        }

Ce code est assez simple à comprendre, mais mérite quand même quelques commentaires :

  • Le premier binding sur cette propriété récupère d’abord une valeur nulle, mais déclenche aussi la récupération asynchrone de la valeur. Notez le flag _retrievingValue qui évite de lancer plusieurs fois la récupération
  • Quand la récupération de la valeur est terminée, la propriété est mise à jour, et l’évènement PropertyChanged met à jour le binding
  • Un détail intéressant est que la propriété est mise à jour directement dans le thread de travail. Puisque cette mise à jour provoque une modification de la vue, on aurait pu s’attendre à une InvalidOperationException, car on ne peut pas modifier la vue à partir d’un autre thread… mais en fait, le mécanisme de binding de WPF est lui-même asynchrone, ce qui masque la complexité de l’appel cross-thread. Il est donc inutile de recourir à un Dispatcher.Invoke ou autre pirouette, ce qui simplifie bien la vie des développeurs que nous sommes…
  • Attention : ce système de binding asynchrone fonctionne bien pour affecter une valeur à une propriété du ViewModel, mais ne permet pas, par exemple, de modifier les éléments d’une ObservableCollection. Si vous essayez, à partir d’un autre thread, d’ajouter ou enlever des éléments à une collection sur laquelle la vue est bindée, cela provoquera une NotSupportedException :

    Ce type de CollectionView ne prend pas en charge les modifications de son SourceCollection à partir d’un thread différent du thread du Dispatcher.

    Pour modifier des collections de façon asynchrone, il faudra donc se débrouiller autrement… cela fera l’objet d’un prochain billet si je trouve une solution satisfaisante (peut-être à base d’AsyncOperation…). Si vous avez une idée là-dessus, n’hésitez pas à la poster en commentaire !

    Fermons cette parenthèse et revenons à notre propriété Value. La méthode décrite plus haut fonctionne bien et nécessite assez peu de code, mais elle a un inconvénient : pendant la récupération de la valeur, l’utilisateur ne voit rien… On aimerait pouvoir afficher quelque chose qui indique que l’application travaille. Pour ça, on peut introduire une propriété IsValueReady qui indiquera si la valeur est prête. Côté XAML, on pourra utiliser un Trigger sur cette propriété pour modifier l’affichage.

            private bool _retrievingValue = false;
    
            private object _value;
            public object Value
            {
                get
                {
                    if (!_isValueReady && !_retrievingValue)
                    {
                        _retrievingValue = true;
                        ThreadPool.QueueUserWorkItem(
                            (state) =>
                            {
                                this.Value = _model.RetrieveValue(); // Very long operation...
                                this.IsValueReady = true;
                                _retrievingValue = false;
                            });
                    }
                    return _value;
                }
                set
                {
                    _value = value;
                    OnPropertyChanged("Value");
                }
            }
    
            private bool _isValueReady = false;
            public bool IsValueReady
            {
                get { return _isValueReady; }
                private set
                {
                    _isValueReady = value;
                    OnPropertyChanged("IsValueReady");
                }
            }
    

    Ça commence à faire un code un peu plus conséquent pour une simple propriété, mais ce code est toujours le même… les seules choses qui changent sont le nom de la propriété, son type, et le code qui récupère la valeur. Si on a beaucoup de propriétés de ce genre à créer, on pourrait donc facilement écrire un code snippet qui génèrerait le plus gros du code.

    Avec Blend, il est probablement assez simple de créer un trigger pour prendre en compte la propriété IsValueReady dans la vue… mais je ne me suis toujours pas mis à Blend, je code directement la vue en XAML, et je trouve les Triggers beaucoup trop lourds à écrire… J’ai donc utilisé une autre solution que je trouve beaucoup plus simple et plus lisible, à base de markup extension (oui, j’aime bien les markup extensions…). Ca donne le XAML suivant :

        <Grid>
            <TextBlock Text="{Binding Value, FallbackValue=Blabla}"
                       Visibility="{my:SwitchBinding IsValueReady, Visible, Hidden}"/>
            <ProgressBar IsIndeterminate="True" Width="150" Height="30"
                         Visibility="{my:SwitchBinding IsValueReady, Hidden, Visible}"/>
        </Grid>
    

    Ce code masque le TextBlock et affiche la ProgressBar tant que la valeur n’est pas prête. Quand la récupération de la valeur est terminée, la ProgressBar disparait et le TextBlock redevient visible…

    SwitchBinding est une markup extension qui hérite de Binding et renvoie une valeur ou une autre selon que la propriété bindée vaut true ou false. Je ne m’étendrai pas sur le fonctionnement de cette extension, car ce n’est pas le sujet de ce billet, mais voici tout de même son code :

        public class SwitchBindingExtension : Binding
        {
            public SwitchBindingExtension()
            {
                Initialize();
            }
    
            public SwitchBindingExtension(string path)
                : base(path)
            {
                Initialize();
            }
    
            public SwitchBindingExtension(string path, object valueIfTrue, object valueIfFalse)
                : base(path)
            {
                Initialize();
                this.ValueIfTrue = valueIfTrue;
                this.ValueIfFalse = valueIfFalse;
            }
    
            private void Initialize()
            {
                this.ValueIfTrue = Binding.DoNothing;
                this.ValueIfFalse = Binding.DoNothing;
                this.Converter = new SwitchConverter(this);
            }
    
            [ConstructorArgument("valueIfTrue")]
            public object ValueIfTrue { get; set; }
    
            [ConstructorArgument("valueIfFalse")]
            public object ValueIfFalse { get; set; }
    
            private class SwitchConverter : IValueConverter
            {
                public SwitchConverter(SwitchBindingExtension switchExtension)
                {
                    _switch = switchExtension;
                }
    
                private SwitchBindingExtension _switch;
    
                #region IValueConverter Members
    
                public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
                {
                    try
                    {
                        bool b = System.Convert.ToBoolean(value);
                        return b ? _switch.ValueIfTrue : _switch.ValueIfFalse;
                    }
                    catch
                    {
                        return DependencyProperty.UnsetValue;
                    }
                }
    
                public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
                {
                    return Binding.DoNothing;
                }
    
                #endregion
            }
    
        }
    

    Cette markup extension est en fait l’équivalent XAML de l’opérateur conditionnel de C# (condition ? valeurSiVrai : valeurSiFaux), et peut être utilisée pour toutes sortes de valeurs.

    Une autre option pour réaliser le comportement voulu aurait été de créer des propriétés qui renvoient une valeur de Visibility selon la valeur de IsValueReady, mais ça fait encore 2 propriétés de plus à créer, ce qui alourdit pas mal le ViewModel.

    Voilà, c’est tout pour aujourd’hui… N’hésitez pas à me faire part de vos commentaires ou suggestions 😉

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

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