[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 !

[C# 4.0] Implémenter un objet dynamique personnalisé

Comme vous le savez sans doute déjà si vous vous intéressez à l’actualité de .NET, la future version 4.0 de C#, actuellement en beta, introduit un nouveau type appelé dynamic. Celui ci permet d’accéder à des propriétés ou méthodes d’un objet qui ne sont pas connus statiquement (à la compilation). Ils seront résolus dynamiquement à l’exécution grâce au DLR (Dynamic Language Runtime), qui est l’une des grandes nouveautés de .NET 4.0. Cela permet notamment de faciliter la manipulation d’objets COM, ou de tout autre objet dont on ne connait pas statiquement le type. Pour plus d’informations sur le type dynamic, je vous invite à consulter la documentation MSDN.

En jouant un peu avec la beta de Visual Studio 2010, je me suis rendu compte qu’on pouvait faire des choses très intéressantes avec ce type dynamic… En effet, il est possible de créer ses propres objets dynamiques, en contrôlant comment sont évalués les appels aux membres de l’objet. Il faut pour cela implémenter l’interface IDynamicMetaObjectProvider. Cette interface semble simple à première vue, dans la mesure où elle ne définit qu’un seul membre : la méthode GetMetaObject. Là où ça se complique un peu, c’est pour implémenter cette méthode… Il faut construire un DynamicMetaObject à partir d’une Expression, et j’avoue que je me suis tout d’abord découragé en voyant la complexité de la tâche.

Heureusement, il y a une technique beaucoup plus simple pour implémenter ses propres objets dynamiques : il suffit d’hériter de la classe DynamicObject, qui fournit une implementation de base de IDynamicMetaObjectProvider. Il suffit ensuite de redéfinir les méthodes qui nous intéressent pour obtenir le comportement souhaité.

Pour le premier exemple, on va s’inspirer du langage Javascript, dans lequel il est possible d’ajouter des membres (propriétés ou méthodes) à un objet existant, de la façon suivante :

var x = new Object();
x.Message = "Hello world !";
x.ShowMessage = function()
{
  alert(this.Message);
};
x.ShowMessage();

Ce code crée un objet, lui ajoute une propriété Message en définissant sa valeur, et ajoute également une méthode ShowMessage qui affiche le message.

Dans les précédentes versions de C#, il était impossible de réaliser une telle chose : en effet C# est un langage à typage statique, ce qui implique que l’accès aux membres d’un objet est résolu à la compilation, et non à l’exécution. La classe Object n’ayant pas de propriété Message ou de méthode ShowMessage, on ne peut pas écrire x.Message ou x.ShowMessage(). Et c’est là qu’entre en jeu le type dynamic

Nous allons donc créer un objet dynamique qui permet d’écrire en C# un code similaire au code Javascript ci-dessus. Pour cela, on va stocker dans un Dictionary<string, object> les valeurs des propriétés définies dynamiquement pour l’objet. La clé du fonctionnement de cette classe est de redéfinir les méthodes TryGetMember et TrySetMember. Ces méthodes implementent la logique permettant de lire ou d’écrire un membre de l’objet dynamique. Pour fixer les idées, voici le code, je le commenterai plus loin.

public class MyDynamicObject : DynamicObject
{
    private Dictionary<string, object> _properties = new Dictionary<string, object>();

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        return _properties.TryGetValue(binder.Name, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        _properties[binder.Name] = value;
        return true;
    }
}

Détaillons maintenant le code ci-dessus… La méthode TryGetMember tente d’obtenir la propriété demandée à partir du dictionnaire. Notez que le nom de la propriété est accessible via la propriété Name du paramètre binder. Si la propriété existe, sa valeur est renvoyée via le paramètre de sortie result et la méthode renvoie true. Dans le cas contraire, la méthode renvoie false, ce qui cause une exception de type RuntimeBinderException à l’endroit où la propriété est accédée en lecture. Cette erreur indique simplement l’échec de la résolution dynamique de la propriété.

La méthode TrySetMember effectue l’opération inverse : elle définit la valeur d’une propriété. Si le membre auquel on veut accéder n’existe pas, il est ajouté au dictionnaire, la méthode renvoie donc toujours true.

Voyons maintenant comment utiliser cet objet :

dynamic x = new MyDynamicObject();
x.Message = "Hello world !";
Console.WriteLine(x.Message);

Ce code fonctionne sans problème et affiche “Hello world !” dans la console… sympa, non ?

Et les méthodes dans tout ça ? Et bien, je pourrais vous dire qu’il faut redéfinir la méthode TryInvokeMember, qui sert à gérer les appels dynamiques de méthodes… Mais en fait, ce n’est même pas nécessaire ! Notre implémentation gère déjà cette fonctionnalité : il suffit d’affecter un delegate à une propriété de l’objet. Ce ne sera donc pas réellement une méthode membre de l’objet, mais plutôt une propriété qui renvoie un Delegate ; mais puisque le code pour invoquer ce delegate est identique à celui de l’appel d’une méthode, on s’en contentera pour l’instant ;). Voilà un exemple d’ajout d’une méthode à l’objet :

dynamic x = new MyDynamicObject();
x.Message = "Hello world !";
x.ShowMessage = new Action(
    () =>
    {
        Console.WriteLine(x.Message);
    });
x.ShowMessage();

On a donc, à peu de choses près, un code identique au code Javascript qu’on voulait imiter. Et tout ça avec une classe de moins de 10 lignes de code (sans compter les accolades)…

Cette petite classe peut être assez pratique comme objet à tout faire, par exemple pour regrouper des informations sans avoir à créer une classe spécifique. En cela elle est similaire à un type anonyme (déjà présent en C# 3), mais avec l’avantage qu’on peut utiliser l’objet comme valeur de retour d’une méthode, ce qui n’était pas possible avec un type anonyme.

Il est bien sûr possible de créer des objets dynamiques plus utiles que celui-ci… par exemple, voilà un petit wrapper pour un DataRow, pour faciliter l’accès aux champs :

public class DynamicDataRow : DynamicObject
{
    private DataRow _dataRow;

    public DynamicDataRow(DataRow dataRow)
    {
        if (dataRow == null)
            throw new ArgumentNullException("dataRow");
        this._dataRow = dataRow;
    }

    public DataRow DataRow
    {
        get { return _dataRow; }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = null;
        if (_dataRow.Table.Columns.Contains(binder.Name))
        {
            result = _dataRow[binder.Name];
            return true;
        }
        return false;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        if (_dataRow.Table.Columns.Contains(binder.Name))
        {
            _dataRow[binder.Name] = value;
            return true;
        }
        return false;
    }
}

Ajoutons une petite méthode d’extension pour rendre plus naturelle l’utilisation de ce wrapper :

public static class DynamicDataRowExtensions
{
    public static dynamic AsDynamic(this DataRow dataRow)
    {
        return new DynamicDataRow(dataRow);
    }
}

On peut maintenant écrire un code de ce genre :

DataTable table = new DataTable();
table.Columns.Add("FirstName", typeof(string));
table.Columns.Add("LastName", typeof(string));
table.Columns.Add("DateOfBirth", typeof(DateTime));

dynamic row = table.NewRow().AsDynamic();
row.FirstName = "John";
row.LastName = "Doe";
row.DateOfBirth = new DateTime(1981, 9, 12);
table.Rows.Add(row.DataRow);

// Add more rows...
// ...

var bornInThe20thCentury = from r in table.AsEnumerable()
                           let dr = r.AsDynamic()
                           where dr.DateOfBirth.Year > 1900
                           && dr.DateOfBirth.Year <= 2000
                           select new { dr.LastName, dr.FirstName };

foreach (var item in bornInThe20thCentury)
{
    Console.WriteLine("{0} {1}", item.FirstName, item.LastName);
}

Voilà, maintenant que vous connaissez le principe, vous pouvez donner libre cours à votre imagination 🙂

Mise à jour : Aussitôt après la publication de cet article, voilà que je tombe sur la classe ExpandoObject, qui fait exactement la même chose que la classe MyDynamicObject ci-dessus… il semblerait donc que j’ai encore réinventé la roue ! Cela dit, il est toujours intéressant de voir le fonctionnement d’un objet dynamique, ne serait-ce qu’à des fins didactiques ;). Pour plus d’infos sur ExpandoObject, voir ce billet sur le blog C# FAQ

[WPF] Markup extensions et templates

Note : Ce billet est la suite de celui sur une markup extension qui met à jour sa cible, et réutilise le même code de départ.

Vous avez peut-être remarqué que l’utilisation d’une markup extension personnalisée dans un template donnait parfois des résultats inattendus… Nous allons voir dans ce billet comment faire une markup extension qui se comporte correctement dans un template.

Illustration du problème

Reprenons l’exemple du précédent billet : une markup extension qui renvoie l’état de la connectivité réseau, et met à jour la propriété cible quand le réseau est connecté ou déconnecté :

<CheckBox IsChecked="{my:NetworkAvailable}" Content="Network is available" />

Mettons maintenant la même CheckBox dans un ControlTemplate :

<ControlTemplate x:Key="test">
  <CheckBox IsChecked="{my:NetworkAvailable}" Content="Network is available" />
</ControlTemplate>

Et créons un contrôle qui utilise ce template :

<Control Template="{StaticResource test}" />

Si on se déconnecte du réseau, on remarque que la CheckBox n’est pas automatiquement mise à jour par la NetworkAvailableExtension, alors que ça fonctionnait bien quand on l’utilisait hors du template…

Explication et solution

La markup expression est évaluée quand elle est rencontrée par le parser XAML : en l’occurrence, lors du parsing du template. Or, à cet instant le contrôle CheckBox n’est pas encore créé, la méthode ProvideValue ne peut donc pas y accéder… Quand une markup extension est évaluée dans un template, le TargetObject est en fait un objet de type System.Windows.SharedDp, qui est une classe interne de WPF.

Pour que la markup extension puisse accéder à sa cible, il faut qu’elle soit évaluée lorsque le template est appliqué : on doit donc retarder son évaluation. Pour y arriver, il suffit en fait de renvoyer l’extension elle-même comme valeur de retour de ProvideValue : de cette façon, elle sera de nouveau évaluée lors de la création du contrôle cible.

Pour savoir si l’extension est appelée pour le template ou pour un contrôle “réel”, il suffit de tester si le type du TargetObject est System.Windows.SharedDp. Le code de la méthode ProvideValue devient donc :

        public sealed override object ProvideValue(IServiceProvider serviceProvider)
        {
            IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            if (target != null)
            {
                if (target.TargetObject.GetType().FullName == "System.Windows.SharedDp")
                    return this;
                _targetObject = target.TargetObject;
                _targetProperty = target.TargetProperty;
            }

            return ProvideValueInternal(serviceProvider);
        }

Voilà, c’est réparé, la CheckBox se met à nouveau à jour en cas de changement de connectivité réseau 🙂

Encore un os

Mais ne crions pas victoire trop vite, on n’est pas encore tout à fait au bout de nos peines… Que se passe-t-il si on souhaite maintenant utiliser notre ControlTemplate sur plusieurs contrôles ?

<Control Template="{StaticResource test}" />
<Control Template="{StaticResource test}" />

Résultat : la seconde checkbox se met à jour, mais pas la première…

La raison est simple : il y a deux contrôles CheckBox, mais une seule instance de NetworkAvailableExtension, partagée entre toutes les instances du template. Or NetworkAvailableExtension ne peut référencer qu’un seul objet cible, c’est donc le dernier pour lequel ProvideValue a été appelée qui est conservé…

Il suffit donc de gérer non pas un objet cible, mais une collection d’objets cibles, qui seront tous mis à jour dans la méthode UpdateValue. Voilà le code final de la classe de base UpdatableMarkupExtension :

    public abstract class UpdatableMarkupExtension : MarkupExtension
    {
        private List<object> _targetObjects = new List<object>();
        private object _targetProperty;

        protected IEnumerable<object> TargetObjects
        {
            get { return _targetObjects; }
        }

        protected object TargetProperty
        {
            get { return _targetProperty; }
        }

        public sealed override object ProvideValue(IServiceProvider serviceProvider)
        {
            // Retrieve target information
            IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

            if (target != null && target.TargetObject != null)
            {
                // In a template the TargetObject is a SharedDp (internal WPF class)
                // In that case, the markup extension itself is returned to be re-evaluated later
                if (target.TargetObject.GetType().FullName == "System.Windows.SharedDp")
                    return this;

                // Save target information for later updates
                _targetObjects.Add(target.TargetObject);
                _targetProperty = target.TargetProperty;
            }

            // Delegate the work to the derived class
            return ProvideValueInternal(serviceProvider);
        }

        protected virtual void UpdateValue(object value)
        {
            if (_targetObjects.Count > 0)
            {
                // Update the target property of each target object
                foreach (var target in _targetObjects)
                {
                    if (_targetProperty is DependencyProperty)
                    {
                        DependencyObject obj = target as DependencyObject;
                        DependencyProperty prop = _targetProperty as DependencyProperty;

                        Action updateAction = () => obj.SetValue(prop, value);

                        // Check whether the target object can be accessed from the
                        // current thread, and use Dispatcher.Invoke if it can't

                        if (obj.CheckAccess())
                            updateAction();
                        else
                            obj.Dispatcher.Invoke(updateAction);
                    }
                    else // _targetProperty is PropertyInfo
                    {
                        PropertyInfo prop = _targetProperty as PropertyInfo;
                        prop.SetValue(target, value, null);
                    }
                }
            }
        }

        protected abstract object ProvideValueInternal(IServiceProvider serviceProvider);
    }

La classe UpdatableMarkupExtension est donc maintenant pleinement opérationnelle… jusqu’à preuve du contraire ;). Cette classe constitue une bonne base pour toute markup extension devant mettre à jour sa cible, sans avoir à se préoccuper des aspects “bas niveau” du suivi des objets cibles et de leur mise à jour.

[WPF] Tri automatique d’un GridView : suite

Il y a quelques mois, j’avais publié un billet où j’expliquais comment trier automatiquement un GridView lors du clic sur un en-tête de colonne. J’avais laissé un point ouvert : l’ajout d’un symbole dans l’en-tête de colonne pour indiquer visuellement la direction du tri. C’est maintenant chose faite !

Exemple GridViewSort avec symbole de tri
Exemple GridViewSort avec symbole de tri

Pour arriver à ce résultat, j’ai utilisé un Adorner : c’est un composant qui permet de dessiner “par-dessus” un élément graphique existant, sur une couche de dessin indépendante.

La nouvelle version de la classe GridViewSort peut s’utiliser de la même façon qu’avant, la grille affiche alors des symboles de tri par défaut. Ils ne sont pas particulièrement “jolis”, donc si vous vous sentez une âme d’artiste, vous pouvez fournir vos propres images de la façon suivante :

        <ListView ItemsSource="{Binding Persons}"
                  IsSynchronizedWithCurrentItem="True"
                  util:GridViewSort.AutoSort="True"
                  util:GridViewSort.SortGlyphAscending="/Images/up.png"
                  util:GridViewSort.SortGlyphDescending="/Images/down.png">

Il est aussi possible de désactiver les symboles de tri, en mettant à false la propriété attachée ShowSortGlyph :

        <ListView ItemsSource="{Binding Persons}"
                  IsSynchronizedWithCurrentItem="True"
                  util:GridViewSort.AutoSort="True"
                  util:GridViewSort.ShowSortGlyph="False">

Notez qu’en l’état actuel, la gestion du symbole de tri ne fonctionne qu’en mode de tri automatique (AutoSort = true). Le cas d’un tri personnalisé avec la propriété Command n’est pas encore géré.

Voici le code complet de la nouvelle classe GridViewSort (un peu plus dense que le précédent…) :

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Documents;

namespace Wpf.Util
{
    public class GridViewSort
    {
        #region Public attached properties

        public static ICommand GetCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(CommandProperty);
        }

        public static void SetCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(CommandProperty, value);
        }

        // Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached(
                "Command",
                typeof(ICommand),
                typeof(GridViewSort),
                new UIPropertyMetadata(
                    null,
                    (o, e) =>
                    {
                        ItemsControl listView = o as ItemsControl;
                        if (listView != null)
                        {
                            if (!GetAutoSort(listView)) // Don't change click handler if AutoSort enabled
                            {
                                if (e.OldValue != null && e.NewValue == null)
                                {
                                    listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                                if (e.OldValue == null && e.NewValue != null)
                                {
                                    listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                            }
                        }
                    }
                )
            );

        public static bool GetAutoSort(DependencyObject obj)
        {
            return (bool)obj.GetValue(AutoSortProperty);
        }

        public static void SetAutoSort(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoSortProperty, value);
        }

        // Using a DependencyProperty as the backing store for AutoSort.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AutoSortProperty =
            DependencyProperty.RegisterAttached(
                "AutoSort",
                typeof(bool),
                typeof(GridViewSort),
                new UIPropertyMetadata(
                    false,
                    (o, e) =>
                    {
                        ListView listView = o as ListView;
                        if (listView != null)
                        {
                            if (GetCommand(listView) == null) // Don't change click handler if a command is set
                            {
                                bool oldValue = (bool)e.OldValue;
                                bool newValue = (bool)e.NewValue;
                                if (oldValue && !newValue)
                                {
                                    listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                                if (!oldValue && newValue)
                                {
                                    listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                            }
                        }
                    }
                )
            );

        public static string GetPropertyName(DependencyObject obj)
        {
            return (string)obj.GetValue(PropertyNameProperty);
        }

        public static void SetPropertyName(DependencyObject obj, string value)
        {
            obj.SetValue(PropertyNameProperty, value);
        }

        // Using a DependencyProperty as the backing store for PropertyName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PropertyNameProperty =
            DependencyProperty.RegisterAttached(
                "PropertyName",
                typeof(string),
                typeof(GridViewSort),
                new UIPropertyMetadata(null)
            );

        public static bool GetShowSortGlyph(DependencyObject obj)
        {
            return (bool)obj.GetValue(ShowSortGlyphProperty);
        }

        public static void SetShowSortGlyph(DependencyObject obj, bool value)
        {
            obj.SetValue(ShowSortGlyphProperty, value);
        }

        // Using a DependencyProperty as the backing store for ShowSortGlyph.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ShowSortGlyphProperty =
            DependencyProperty.RegisterAttached("ShowSortGlyph", typeof(bool), typeof(GridViewSort), new UIPropertyMetadata(true));

        public static ImageSource GetSortGlyphAscending(DependencyObject obj)
        {
            return (ImageSource)obj.GetValue(SortGlyphAscendingProperty);
        }

        public static void SetSortGlyphAscending(DependencyObject obj, ImageSource value)
        {
            obj.SetValue(SortGlyphAscendingProperty, value);
        }

        // Using a DependencyProperty as the backing store for SortGlyphAscending.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SortGlyphAscendingProperty =
            DependencyProperty.RegisterAttached("SortGlyphAscending", typeof(ImageSource), typeof(GridViewSort), new UIPropertyMetadata(null));

        public static ImageSource GetSortGlyphDescending(DependencyObject obj)
        {
            return (ImageSource)obj.GetValue(SortGlyphDescendingProperty);
        }

        public static void SetSortGlyphDescending(DependencyObject obj, ImageSource value)
        {
            obj.SetValue(SortGlyphDescendingProperty, value);
        }

        // Using a DependencyProperty as the backing store for SortGlyphDescending.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SortGlyphDescendingProperty =
            DependencyProperty.RegisterAttached("SortGlyphDescending", typeof(ImageSource), typeof(GridViewSort), new UIPropertyMetadata(null));

        #endregion

        #region Private attached properties

        private static GridViewColumnHeader GetSortedColumnHeader(DependencyObject obj)
        {
            return (GridViewColumnHeader)obj.GetValue(SortedColumnHeaderProperty);
        }

        private static void SetSortedColumnHeader(DependencyObject obj, GridViewColumnHeader value)
        {
            obj.SetValue(SortedColumnHeaderProperty, value);
        }

        // Using a DependencyProperty as the backing store for SortedColumn.  This enables animation, styling, binding, etc...
        private static readonly DependencyProperty SortedColumnHeaderProperty =
            DependencyProperty.RegisterAttached("SortedColumnHeader", typeof(GridViewColumnHeader), typeof(GridViewSort), new UIPropertyMetadata(null));

        #endregion

        #region Column header click event handler

        private static void ColumnHeader_Click(object sender, RoutedEventArgs e)
        {
            GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
            if (headerClicked != null && headerClicked.Column != null)
            {
                string propertyName = GetPropertyName(headerClicked.Column);
                if (!string.IsNullOrEmpty(propertyName))
                {
                    ListView listView = GetAncestor<ListView>(headerClicked);
                    if (listView != null)
                    {
                        ICommand command = GetCommand(listView);
                        if (command != null)
                        {
                            if (command.CanExecute(propertyName))
                            {
                                command.Execute(propertyName);
                            }
                        }
                        else if (GetAutoSort(listView))
                        {
                            ApplySort(listView.Items, propertyName, listView, headerClicked);
                        }
                    }
                }
            }
        }

        #endregion

        #region Helper methods

        public static T GetAncestor<T>(DependencyObject reference) where T : DependencyObject
        {
            DependencyObject parent = VisualTreeHelper.GetParent(reference);
            while (!(parent is T))
            {
                parent = VisualTreeHelper.GetParent(parent);
            }
            if (parent != null)
                return (T)parent;
            else
                return null;
        }

        public static void ApplySort(ICollectionView view, string propertyName, ListView listView, GridViewColumnHeader sortedColumnHeader)
        {
            ListSortDirection direction = ListSortDirection.Ascending;
            if (view.SortDescriptions.Count > 0)
            {
                SortDescription currentSort = view.SortDescriptions[0];
                if (currentSort.PropertyName == propertyName)
                {
                    if (currentSort.Direction == ListSortDirection.Ascending)
                        direction = ListSortDirection.Descending;
                    else
                        direction = ListSortDirection.Ascending;
                }
                view.SortDescriptions.Clear();

                GridViewColumnHeader currentSortedColumnHeader = GetSortedColumnHeader(listView);
                if (currentSortedColumnHeader != null)
                {
                    RemoveSortGlyph(currentSortedColumnHeader);
                }
            }
            if (!string.IsNullOrEmpty(propertyName))
            {
                view.SortDescriptions.Add(new SortDescription(propertyName, direction));
                if (GetShowSortGlyph(listView))
                    AddSortGlyph(
                        sortedColumnHeader,
                        direction,
                        direction == ListSortDirection.Ascending ? GetSortGlyphAscending(listView) : GetSortGlyphDescending(listView));
                SetSortedColumnHeader(listView, sortedColumnHeader);
            }
        }

        private static void AddSortGlyph(GridViewColumnHeader columnHeader, ListSortDirection direction, ImageSource sortGlyph)
        {
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(columnHeader);
            adornerLayer.Add(
                new SortGlyphAdorner(
                    columnHeader,
                    direction,
                    sortGlyph
                    ));
        }

        private static void RemoveSortGlyph(GridViewColumnHeader columnHeader)
        {
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(columnHeader);
            Adorner[] adorners = adornerLayer.GetAdorners(columnHeader);
            if (adorners != null)
            {
                foreach (Adorner adorner in adorners)
                {
                    if (adorner is SortGlyphAdorner)
                        adornerLayer.Remove(adorner);
                }
            }
        }

        #endregion

        #region SortGlyphAdorner nested class

        private class SortGlyphAdorner : Adorner
        {
            private GridViewColumnHeader _columnHeader;
            private ListSortDirection _direction;
            private ImageSource _sortGlyph;

            public SortGlyphAdorner(GridViewColumnHeader columnHeader, ListSortDirection direction, ImageSource sortGlyph)
                : base(columnHeader)
            {
                _columnHeader = columnHeader;
                _direction = direction;
                _sortGlyph = sortGlyph;
            }

            private Geometry GetDefaultGlyph()
            {
                double x1 = _columnHeader.ActualWidth - 13;
                double x2 = x1 + 10;
                double x3 = x1 + 5;
                double y1 = _columnHeader.ActualHeight / 2 - 3;
                double y2 = y1 + 5;

                if (_direction == ListSortDirection.Ascending)
                {
                    double tmp = y1;
                    y1 = y2;
                    y2 = tmp;
                }

                PathSegmentCollection pathSegmentCollection = new PathSegmentCollection();
                pathSegmentCollection.Add(new LineSegment(new Point(x2, y1), true));
                pathSegmentCollection.Add(new LineSegment(new Point(x3, y2), true));

                PathFigure pathFigure = new PathFigure(
                    new Point(x1, y1),
                    pathSegmentCollection,
                    true);

                PathFigureCollection pathFigureCollection = new PathFigureCollection();
                pathFigureCollection.Add(pathFigure);

                PathGeometry pathGeometry = new PathGeometry(pathFigureCollection);
                return pathGeometry;
            }

            protected override void OnRender(DrawingContext drawingContext)
            {
                base.OnRender(drawingContext);

                if (_sortGlyph != null)
                {
                    double x = _columnHeader.ActualWidth - 13;
                    double y = _columnHeader.ActualHeight / 2 - 5;
                    Rect rect = new Rect(x, y, 10, 10);
                    drawingContext.DrawImage(_sortGlyph, rect);
                }
                else
                {
                    drawingContext.DrawGeometry(Brushes.LightGray, new Pen(Brushes.Gray, 1.0), GetDefaultGlyph());
                }
            }
        }

        #endregion
    }
}

Voilà, j’espère que ça vous sera utile :).

[WPF] Une markup extension qui met à jour sa cible

Si vous avez lu mes précédents billets sur le sujet, vous savez que je suis un grand fan des markup extensions… Cependant, celles-ci ont une limitation qui peut s’avérer assez gênante : elles ne sont évaluées qu’une seule fois. Il serait pourtant utile de pouvoir les réévaluer pour mettre à jour la propriété cible, comme pour un binding… Cela peut être utile dans différents cas, notamment :

  • si la valeur de la markup extension peut changer en réponse à un évènement
  • si l’état de l’objet cible quand la markup extension est évaluée ne permet pas encore de déterminer la valeur à renvoyer, et qu’il faut différer l’évaluation (par exemple si l’on a besoin du DataContext de l’objet, et que celui-ci n’est pas encore défini lors de l’évaluation)

Voyons donc comment on peut obtenir le comportement voulu…

La méthode ProvideValue d’une markup extension prend un paramètre de type IServiceProvider, qui fournit, entre autres, un service IProvideValueTarget. Cette interface expose des propriétés TargetObject et TargetProperty, qui permettent d’obtenir l’objet et la propriété cibles de la markup extension. Il est donc possible, si l’on sauvegarde cette information, de mettre à jour la propriété concernée alors que la markup extension a déjà été évaluée.

On va donc créer un classe abstraite UpdatableMarkupExtension, qui sauvegarde l’objet et la propriété cible et fournit une méthode pour mettre à jour la valeur :

    public abstract class UpdatableMarkupExtension : MarkupExtension
    {
        private object _targetObject;
        private object _targetProperty;

        protected object TargetObject
        {
            get { return _targetObject; }
        }

        protected object TargetProperty
        {
            get { return _targetProperty; }
        }

        public sealed override object ProvideValue(IServiceProvider serviceProvider)
        {
            IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            if (target != null)
            {
                _targetObject = target.TargetObject;
                _targetProperty = target.TargetProperty;
            }

            return ProvideValueInternal(serviceProvider);
        }

        protected void UpdateValue(object value)
        {
            if (_targetObject != null)
            {
                if (_targetProperty is DependencyProperty)
                {
                    DependencyObject obj = _targetObject as DependencyObject;
                    DependencyProperty prop = _targetProperty as DependencyProperty;

                    Action updateAction = () =>  obj.SetValue(prop, value);

                    // Check whether the target object can be accessed from the
                    // current thread, and use Dispatcher.Invoke if it can't

                    if (obj.CheckAccess())
                        updateAction();
                    else
                        obj.Dispatcher.Invoke(updateAction);
                }
                else // _targetProperty is PropertyInfo
                {
                    PropertyInfo prop = _targetProperty as PropertyInfo;
                    prop.SetValue(_targetObject, value, null);
                }
            }
        }

        protected abstract object ProvideValueInternal(IServiceProvider serviceProvider);
    }

Comme il est indispensable de sauvegarder l’objet et la propriété cibles, on marque la méthode ProvideValue comme sealed pour qu’elle ne puisse pas être redéfinie, et on définit à la place une méthode abstraite ProvideValueInternal pour que les classes dérivées puissent fournir leur implémentation.

La méthode UpdateValue gère la mise à jour de la propriété cible, qui peut être soit une propriété de dépendance (DependencyProperty), soit une propriété CLR classique (PropertyInfo). Dans le cas d’une DependencyProperty, l’objet cible hérite de DependencyObject, et donc de DispatcherObject : il faut donc s’assurer qu’on n’accède à cet objet qu’à partir du thread qui le possède, à l’aide des méthodes CheckAccess et Invoke.

Voyons maintenant comment utiliser cette classe, au travers d’un exemple simple. Supposons qu’on souhaite réaliser une markup extension qui indique si le réseau est disponible, et qui s’utiliserait de la façon suivante :

<CheckBox IsChecked="{my:NetworkAvailable}" Content="Network is available" />

On souhaite que la checkbox se mette à jour si l’état de la connexion change (cable branché ou débranché, Wifi hors de portée…). Il faut donc gérer l’évènement NetworkChange.NetworkAvailabilityChanged, et mettre à jour la propriété IsChecked en conséquence. Notre extension va donc utiliser les fonctionnalités implémentées dans la classe UpdatableMarkupExtension :

    public class NetworkAvailableExtension : UpdatableMarkupExtension
    {
        public NetworkAvailableExtension()
        {
            NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
        }

        protected override object ProvideValueInternal(IServiceProvider serviceProvider)
        {
            return NetworkInterface.GetIsNetworkAvailable();
        }

        private void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            UpdateValue(e.IsAvailable);
        }
    }

Notez qu’on s’abonne à l’évènement NetworkAvailabilityChanged dans le constructeur de la classe. Si on voulait s’abonner à un évènement de l’objet cible, il faudrait plutôt le faire dans la méthode ProvideValueInternal, pour avoir accès à l’objet cible.

On voit donc qu’il est très simple d’implémenter une markup extension capable de mettre à jour sa cible a posteriori, après la première évaluation. Cela permet d’obtenir un fonctionnement similaire à celui du binding, mais sans être limité aux propriétés de dépendance. J’ai notamment utilisé cette possibilité pour réaliser un système de localisation qui permet de changer la langue “à la volée”, sans redémarrer le programme.

Mise à jour : En l’état actuel, cette markup extension ne supporte pas l’utilisation dans un template. Pour une explication et une solution à ce problème, lisez ce billet.

[C#] Relation parent/enfant et sérialisation XML

Me revoilà avec un peu de retard, j’ai un peu manqué de temps libre ces dernières semaines… Voilà donc un petit post pour présenter une idée qui m’est venue récemment. Pour une fois, il ne sera pas question de WPF, c’est de conception C# qu’il s’agit !

Le problème

Il est assez courant, dans un programme, d’avoir un objet parent qui possède une collection d’enfants ayant une référence vers leur parent. C’est le cas, par exemple, des contrôles Windows Forms, qui possèdent une collection de contrôles enfants (Controls), et une propriété qui indique le contrôle parent (Parent).

Ce type de structure est assez simple à réaliser, cela nécessite juste un peu de code pour maintenir la cohérence de la relation. Là où ça se complique un peu, c’est quand on veut sérialiser l’objet parent en XML… Prenons un exemple simple (purement théorique bien sûr) :

    public class Parent
    {
        public Parent()
        {
            this.Children = new List<Child>();
        }

        public string Name { get; set; }

        public List<Child> Children { get; set; }

        public void AddChild(Child child)
        {
            child.ParentObject = this;
            this.Children.Add(child);
        }

        public void RemoveChild(Child child)
        {
            this.Children.Remove(child);
            child.ParentObject = null;
        }
    }
    public class Child
    {
        public string Name { get; set; }

        public Parent ParentObject { get; set; }
    }

Créons une instance de Parent avec quelques enfants, et essayons de la sérialiser en XML :

            Parent p = new Parent { Name = "The parent" };
            p.AddChild(new Child { Name = "First child" });
            p.AddChild(new Child { Name = "Second child" });

            string xml;
            XmlSerializer xs = new XmlSerializer(typeof(Parent));
            using (StringWriter wr = new StringWriter())
            {
                xs.Serialize(wr, p);
                xml = wr.ToString();
            }

            Console.WriteLine(xml);

Quand on sérialise l’objet Parent, il se produit une InvalidOperationException due à une référence circulaire : en effet, le parent référence les enfants, qui référencent le parent, qui référence les enfants… et ainsi de suite. La première solution qui vient à l’esprit est de ne pas sérialiser la propriété Child.ParentObject : il suffit pour cela de lui appliquer l’attribut XmlIgnore. La sérialisation se passe alors sans problème, mais quand on désérialise, mauvaise surprise : la propriété ParentObject n’est pas renseignée, et pour cause, puisqu’elle n’a pas été sauvegardée…

On pourrait opter pour une solution simple : après la désérialisation : parcourir la liste des enfants pour affecter manuellement la propriété ParentObject. Mais ce n’est vraiment pas très élégant… et comme j’aime bien écrire du beau code, j’ai donc cherché autre chose 😉

La solution

La solution qui m’est venue à l’esprit consiste en une collection générique spécialisée ChildItemCollection<P,T>, et une interface IChildItem<P> que les enfants doivent implémenter.

L’interface IChildItem<P> définit simplement une propriété Parent, de type P :

    /// <summary>
    /// Defines the contract for an object that has a parent object
    /// </summary>
    /// <typeparam name="P">Type of the parent object</typeparam>
    public interface IChildItem<P> where P : class
    {
        P Parent { get; set; }
    }

La collection ChildItemCollection<P,T> implémente IList<T> en délégant l’implémentation à une List<T> ou à la collection passée en paramètre du constructeur, et gère le maintien de la relation parent/enfant :

    /// <summary>
    /// Collection of child items. This collection automatically set the
    /// Parent property of the child items when they are added or removed
    /// </summary>
    /// <typeparam name="P">Type of the parent object</typeparam>
    /// <typeparam name="T">Type of the child items</typeparam>
    public class ChildItemCollection<P, T> : IList<T>
        where P : class
        where T : IChildItem<P>
    {
        private P _parent;
        private IList<T> _collection;

        public ChildItemCollection(P parent)
        {
            this._parent = parent;
            this._collection = new List<T>();
        }

        public ChildItemCollection(P parent, IList<T> collection)
        {
            this._parent = parent;
            this._collection = collection;
        }

        #region IList<T> Members

        public int IndexOf(T item)
        {
            return _collection.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            if (item != null)
                item.Parent = _parent;
            _collection.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            T oldItem = _collection[index];
            _collection.RemoveAt(index);
            if (oldItem != null)
                oldItem.Parent = null;
        }

        public T this[int index]
        {
            get
            {
                return _collection[index];
            }
            set
            {
                T oldItem = _collection[index];
                if (value != null)
                    value.Parent = _parent;
                _collection[index] = value;
                if (oldItem != null)
                    oldItem.Parent = null;
            }
        }

        #endregion

        #region ICollection<T> Members

        public void Add(T item)
        {
            if (item != null)
                item.Parent = _parent;
            _collection.Add(item);
        }

        public void Clear()
        {
            foreach (T item in _collection)
            {
                if (item != null)
                    item.Parent = null;
            }
            _collection.Clear();
        }

        public bool Contains(T item)
        {
            return _collection.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            _collection.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get { return _collection.Count; }
        }

        public bool IsReadOnly
        {
            get { return _collection.IsReadOnly; }
        }

        public bool Remove(T item)
        {
            bool b = _collection.Remove(item);
            if (item != null)
                item.Parent = null;
            return b;
        }

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return _collection.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return (_collection as System.Collections.IEnumerable).GetEnumerator();
        }

        #endregion
    }

Voyons donc comment utiliser cette solution dans notre exemple précédent… Nous allons d’abord modifier la classe Child pour qu’elle implémente l’interface IChildItem<Parent> :

    public class Child : IChildItem<Parent>
    {
        public string Name { get; set; }

        [XmlIgnore]
        public Parent ParentObject { get; internal set; }

        #region IChildItem<Parent> Members

        Parent IChildItem<Parent>.Parent
        {
            get
            {
                return this.ParentObject;
            }
            set
            {
                this.ParentObject = value;
            }
        }

        #endregion
    }

Vous remarquerez qu’on implémente l’interface IChildItem<Parent> de façon explicite : l’intérêt est de “masquer” la propriété Parent, qui ne sera accessible que si on manipule l’objet Child via une variable de type IChildItem<Parent>. On définit aussi l’accesseur set de la propriété ParentObject comme étant internal, de façon à empêcher sa modification à partir d’un autre assembly.

Dans la classe Parent, il suffit de remplacer la List<Child> par une ChildItemCollection<Parent, Child>. On supprime au passage les méthodes AddChild et RemoveChild, qui ne sont plus nécessaires puisque la classe ChildItemCollection<P,T> se charge de renseigner la propriété Parent.

    public class Parent
    {
        public Parent()
        {
            this.Children = new ChildItemCollection<Parent, Child>(this);
        }

        public string Name { get; set; }

        public ChildItemCollection<Parent, Child> Children { get; private set; }
    }

Notez qu’on passe au constructeur de ChildItemCollection<Parent, Child> une référence vers l’objet courant : c’est de cette façon que la collection sait quel doit être le parent de ses éléments.

Le code utilisé précédemment pour sérialiser un Parent fonctionne maintenant correctement. Lors de la désérialisation, la propriété Child.ParentObject n’est pas affectée quand le Child est désérialisé (puisqu’elle a l’attribut XmlIgnore), mais lors de l’ajout à la collection Parent.Children.

Cette solution permet donc de conserver la relation parent/enfant lors de la sérialisation XML, sans recourir à des bricolages peu élégants… Notez cependant une restriction : si la propriété ParentObject est modifiée autrement que par la classe ChildItemCollection<P,T>, la cohérence de la relation parent/enfant est rompue. Il est cependant assez simple d’ajouter à l’accesseur set la logique pour maintenir la cohérence, je ne l’ai pas fait dans cet exemple par souci de clarté et de simplicité.

[Windows Forms] Glisser-déplacer automatique de contrôles (DragMove)

Je ressors de mes tiroirs un code que j’avais écrit il y a quelque temps, et dont je voudrais vous faire profiter…

Il existe en WPF une méthode très pratique pour déplacer une fenêtre sans bordure : Window.DragMove. Elle s’utilise comme ceci :

        private void Window_MouseDown(object sender, MouseButtonEventArgs e)
        {
            this.DragMove();
        }

A partir de l’appel de cette méthode, la fenêtre est déplacée avec la souris jusqu’à ce que le bouton soit relâché. Difficile de faire plus simple 😉

Malheureusement, cette méthode n’existe qu’en WPF, et une majorité de développeurs travaillent encore avec Windows Forms. Je vous propose donc un code qui permet d’utiliser en Windows Forms la technique décrite ci-dessus, en y apportant les améliorations suivantes :

  • Utilisable sur n’importe quel contrôle, pas seulement sur une fenêtre
  • Pas d’obligation de gérer l’évènement MouseDown
  • Intégration au designer par un IExtenderProvider

Ma solution se compose des 2 éléments suivants :

  • une classe statique DragMoveExtensions qui fournit des méthodes d’extensions pour la classe Control (facilement transformables en simples méthodes statiques pour l’utilisation en C# 2)
  • un composant DragMoveProvider, qui implémente IExtenderProvider pour ajouter une propriété EnableDragMove aux contrôles de la Form

Pour l’utiliser, il y en a pour tous les goûts 😉

  • La méthode la plus simple, sans écrire une seule ligne de code : en mode design, poser un DragMoveProvider sur la Form, et positionner à true la propriété EnableDragMove sur la Form ou le contrôle
  • DragMoveProvider
    DragMoveProvider
  • La méthode la plus proche de WPF : dans le handler de l’évènement MouseDown, appeler la méthode d’extension DragMove sur la Form ou le contrôle à déplacer
  •         private void label2_MouseDown(object sender, MouseEventArgs e)
            {
                label2.DragMove();
            }
    
  • La méthode la plus souple : appeler, au cas par cas, la méthode d’extension EnableDragMove sur la Form ou le contrôle à déplacer (aucun évènement à gérer).
  •         private void checkBox1_CheckedChanged(object sender, EventArgs e)
            {
                this.EnableDragMove(checkBox1.Checked);
            }
    

La solution en pièce jointe contient la librairie réutilisable WinFormsDragMove, ainsi qu’un projet de test qui illustre les différentes manières d’utiliser cette librairie. Une version compatible C# 2 des ces projets est également inclue.

Télécharger les sources

[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] Tri automatique d’un GridView lors du clic sur une colonne

Il est assez simple, en WPF, de présenter des données sous forme de grille, grâce à la classe GridView. Pour le tri, en revanche, ça se complique… Avec le DataGridView de Windows Forms, c’était “automagique” : quand l’utilisateur cliquait sur un en-tête de colonne, le tri se faisait automatiquement sur cette colonne. En WPF, par contre, il faut un peu mettre les mains dans le cambouis… La méthode préconisée par Microsoft pour trier un GridView lors du clic sur une colonne est décrite dans cet article ; elle est basée sur l’évènement Click du GridViewColumnHeader. A mon sens, cette méthode présente deux gros inconvénients :

  • Le tri doit être réalisé dans le code-behind, ce qu’on préfère souvent éviter si on s’appuie sur un design pattern comme MVVM. De plus, cela rend le code moins facilement réutilisable
  • Cette méthode suppose que le texte de l’en-tête de la colonne correspond au nom de la propriété sur laquelle on veut trier. Ce qui, bien sûr, est loin d’être toujours le cas… On pourrait se baser sur le DisplayMemberBinding de la colonne, mais il n’est pas forcément défini (par exemple si on définit un CellTemplate à la place).

Après avoir longuement tâtonné pour trouver une approche souple et élégante, j’ai fini par réaliser une classe GridViewSort qui permet de trier automatiquement un GridView selon des propriétés attachées définies en XAML.

On utilise cette classe de la façon suivante :

                <ListView ItemsSource="{Binding Persons}"
                      IsSynchronizedWithCurrentItem="True"
                      util:GridViewSort.AutoSort="True">
                    <ListView.View>
                        <GridView>
                            <GridView.Columns>
                                <GridViewColumn Header="Nom"
                                                DisplayMemberBinding="{Binding Name}"
                                                util:GridViewSort.PropertyName="Name"/>
                                <GridViewColumn Header="Prénom"
                                                DisplayMemberBinding="{Binding FirstName}"
                                                util:GridViewSort.PropertyName="FirstName"/>
                                <GridViewColumn Header="Date de naissance"
                                                DisplayMemberBinding="{Binding DateOfBirth}"
                                                util:GridViewSort.PropertyName="DateOfBirth"/>
                            </GridView.Columns>
                        </GridView>
                    </ListView.View>
                </ListView>

La propriété GridViewSort.AutoSort active le tri automatique pour la ListView. La propriété GridViewSort.PropertyName, définie sur chaque colonne, indique sur quelle propriété le tri doit être effectué. Il n’y a aucun code supplémentaire à écrire. Le clic sur un en-tête de colonne déclenche le tri sur cette colonne ; si la ListView est déjà triée sur cette colonne, l’ordre de tri est inversé.

Pour le cas où on souhaiterait gérer manuellement le tri, j’ai aussi créé une propriété attachée GridViewSort.Command. Par exemple, dans le cadre de l’utilisation du pattern MVVM, on peut binder cette propriété sur une commande déclarée dans le ViewModel :

                <ListView ItemsSource="{Binding Persons}"
                      IsSynchronizedWithCurrentItem="True"
                      util:GridViewSort.Command="{Binding SortCommand}">
                ...

La commande de tri reçoit en paramètre le nom de la propriété sur laquelle on veut trier.

Note : si les propriétés Command et AutoSort sont définies toutes les deux, c’est Command qui est prioritaire ; AutoSort est ignorée.

Voici le code complet de la classe GridViewSort :

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace Wpf.Util
{
    public class GridViewSort
    {
        #region Attached properties

        public static ICommand GetCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(CommandProperty);
        }

        public static void SetCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(CommandProperty, value);
        }

        // Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached(
                "Command",
                typeof(ICommand),
                typeof(GridViewSort),
                new UIPropertyMetadata(
                    null,
                    (o, e) =>
                    {
                        ItemsControl listView = o as ItemsControl;
                        if (listView != null)
                        {
                            if (!GetAutoSort(listView)) // Don't change click handler if AutoSort enabled
                            {
                                if (e.OldValue != null && e.NewValue == null)
                                {
                                    listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                                if (e.OldValue == null && e.NewValue != null)
                                {
                                    listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                            }
                        }
                    }
                )
            );

        public static bool GetAutoSort(DependencyObject obj)
        {
            return (bool)obj.GetValue(AutoSortProperty);
        }

        public static void SetAutoSort(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoSortProperty, value);
        }

        // Using a DependencyProperty as the backing store for AutoSort.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AutoSortProperty =
            DependencyProperty.RegisterAttached(
                "AutoSort",
                typeof(bool),
                typeof(GridViewSort),
                new UIPropertyMetadata(
                    false,
                    (o, e) =>
                    {
                        ListView listView = o as ListView;
                        if (listView != null)
                        {
                            if (GetCommand(listView) == null) // Don't change click handler if a command is set
                            {
                                bool oldValue = (bool)e.OldValue;
                                bool newValue = (bool)e.NewValue;
                                if (oldValue && !newValue)
                                {
                                    listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                                if (!oldValue && newValue)
                                {
                                    listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                            }
                        }
                    }
                )
            );

        public static string GetPropertyName(DependencyObject obj)
        {
            return (string)obj.GetValue(PropertyNameProperty);
        }

        public static void SetPropertyName(DependencyObject obj, string value)
        {
            obj.SetValue(PropertyNameProperty, value);
        }

        // Using a DependencyProperty as the backing store for PropertyName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PropertyNameProperty =
            DependencyProperty.RegisterAttached(
                "PropertyName",
                typeof(string),
                typeof(GridViewSort),
                new UIPropertyMetadata(null)
            );

        #endregion

        #region Column header click event handler

        private static void ColumnHeader_Click(object sender, RoutedEventArgs e)
        {
            GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
            if (headerClicked != null)
            {
                string propertyName = GetPropertyName(headerClicked.Column);
                if (!string.IsNullOrEmpty(propertyName))
                {
                    ListView listView = GetAncestor<ListView>(headerClicked);
                    if (listView != null)
                    {
                        ICommand command = GetCommand(listView);
                        if (command != null)
                        {
                            if (command.CanExecute(propertyName))
                            {
                                command.Execute(propertyName);
                            }
                        }
                        else if (GetAutoSort(listView))
                        {
                            ApplySort(listView.Items, propertyName);
                        }
                    }
                }
            }
        }

        #endregion

        #region Helper methods

        public static T GetAncestor<T>(DependencyObject reference) where T : DependencyObject
        {
            DependencyObject parent = VisualTreeHelper.GetParent(reference);
            while (!(parent is T))
            {
                parent = VisualTreeHelper.GetParent(parent);
            }
            if (parent != null)
                return (T)parent;
            else
                return null;
        }

        public static void ApplySort(ICollectionView view, string propertyName)
        {
            ListSortDirection direction = ListSortDirection.Ascending;
            if (view.SortDescriptions.Count > 0)
            {
                SortDescription currentSort = view.SortDescriptions[0];
                if (currentSort.PropertyName == propertyName)
                {
                    if (currentSort.Direction == ListSortDirection.Ascending)
                        direction = ListSortDirection.Descending;
                    else
                        direction = ListSortDirection.Ascending;
                }
                view.SortDescriptions.Clear();
            }
            if (!string.IsNullOrEmpty(propertyName))
            {
                view.SortDescriptions.Add(new SortDescription(propertyName, direction));
            }
        }

        #endregion
    }
}

On pourrait bien sûr envisager certaines améliorations, notamment visuelles, comme l’ajout d’une flèche sur la colonne triée (à l’aide d’un Adorner par exemple). Mais en attendant, cette classe couvre tout l’aspect fonctionnel du tri, donc n’hésitez pas à l’utiliser !

Mise à jour : l’affichage du symbole de tri est maintenant géré par la classe GridViewSort, la nouvelle version est disponible dans ce billet.

css.php