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