Une erreur classique de notification en MVVM

L’implémentation de la notification en MVVM est chose aisée.

Si l’on utilise un objet, on implémentera l’interface INotifyPropertyChanged (ou l’on héritera d’un objet l’implémentant).

Il arrive parfois, Lorsque la structure de l’objet à notifié est complexe que l’on tombe sur l’erreur que nous allons décrire en détail dans cet article.

Imaginons une classe MonObjet contenant une propriété MaPropriete implémentant la notification :

    public class MonObjet : INotifyPropertyChanged
    {
        public const string MaProprietePropertyName = "MaPropriete";

        /// <summary>
        /// Ma propriété
        /// </summary>

        public string MaPropriete
        {
            get
            {
                return this.maPropriete;
            }

            set
            {
                if (this.maPropriete != value)
                {
                    maPropriete = value;
                    // notification du changement
                    this.RaisePropertyChanged( MaProprietePropertyName );
                }
            }
        }

        private string maPropriete = null;

        /// <summary>
        /// implémentation de la notification
        /// </summary>
        /// <param name="propertyName"></param>
 

        protected void RaisePropertyChanged( string propertyName )
        {
            if( this.PropertyChanged != null )
            {
                this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
            }
        }

        /// <summary>
        /// evenement de changement de la propriété 
        /// </summary>
        
        public event PropertyChangedEventHandler PropertyChanged;
    }

Mettons en place maintenant une classe de plus haut niveau contenant la classe que nous venons de creer. Elle sera notifiable de la même manière que la précedente.
Elle comportera, de plus, la propriete MaProprieteEtoile qui rajoutera des étoiles de chaques coté de la chaine de la propriété MaPropriete appartenant à son enfant MonObjet.

    public class MaRacine : INotifyPropertyChanged
    {
        public const string MonObjetPropertyName = "MonObjet";

        /// <summary>
        /// Ma propriété
        /// </summary>

        public MonObjet MonObjet
        {
            get
            {
                return this.monObjet;
            }

            set
            {
                if (this.monObjet != value)
                {
                    monObjet = value;
                    // notification du changement
                    this.RaisePropertyChanged(MonObjetPropertyName);
                }
            }
        }

        private MonObjet monObjet = new MonObjet();
        public const string MaProprieteEtoilePropertyName = "MaProprieteEtoile";

        /// <summary>
        /// Ma propriété
        /// </summary>

        public string MaProprieteEtoile
        {
            get
            {
                return "**" + this.MonObjet.MaPropriete + "**";
            }
        }

        /// <summary>
        /// implémentation de la notification
        /// </summary>
        /// <param name="propertyName"></param>


        protected void RaisePropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// evenement de changement de la propriété 
        /// </summary>

        public event PropertyChangedEventHandler PropertyChanged;
    }

Globalement on peut accéder à la propriete MaPropriete de la manière suivante :

   MaRacine r = new MaRacine();
   r.MonObjet.MaPropriete = "Miam";
   string resultat = r.MaProprieteEtoile;   
  ///Le resultat est "**Miam**"

La propriete MaProprieteEtoile contiendra bien « **Miam** » mais est-ce le même chose avec la notification ?

Pour le savoir, mettons en place une TextBox et un TextBlock alimentés par ces classes :

   <TextBox Text="{Binding MaRacine.MonObjet.MaPropriete, Mode=TwoWay}"  />
   <TextBlock Text={Binding MaRacine.MaProprieteEtoile}" />

Lorsque l’on change « MaPropriété » via la TextBox, on observe aucun changement dans le TextBlock !
Pourquoi ?

L’erreur réside à penser que lorsque MaPropriete est changée, Les objets parents seront automatiquement notifiés de ce changement.

Comment remédier à ce problème. Pour notifier un changement, on doit être capable de détecter le changement. Ici, on doit être capable de détecter le changement de la propriéré MaPropriete et de notifier un changement dans MaProprieteEtoile.

Comme MonObjet implémente INotifyPropertyChanged, l’évenement PropertyChanged est nécessairement implementé. Donc dans l’Objet MaRacine, on détectera tout changement dans la propriété MonObjet :

        // propriété MonObjet de la classe MaRacine

        public const string MonObjetPropertyName = "MonObjet";

        /// <summary>
        /// Ma propriété
        /// </summary>

        public MonObjet MonObjet
        {
            get
            {
                return this.monObjet;
            }

            set
            {
                if (this.monObjet != value)
                {
                    // détection des changement : abonnement et desabonnement en cas de changement de MonObjet

                    if (monObjet != null)
                    {
                        monObjet.PropertyChanged -= new PropertyChangedEventHandler(monObjet_PropertyChanged);
                    }
                    
                    monObjet = value;

                    if (value != null)
                    {
                        monObjet.PropertyChanged += new PropertyChangedEventHandler(monObjet_PropertyChanged);
                    }

                    // notification du changement
                    this.RaisePropertyChanged(MonObjetPropertyName);
                }
            }
        }

        private MonObjet monObjet = new MonObjet();

        /// <summary>
        /// Changement
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        void monObjet_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // un changement est detectée
        }

Une fois le changement détecté, il suffit de notifier le changement de MaProprieteEtoile

        void monObjet_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // un changement est detectée
            if (e.PropertyName == MonObjet.MaProprietePropertyName)
            {
                // la propriété MaProprieteEtoile est notifiée !
                this.RaisePropertyChanged(MaProprieteEtoilePropertyName);
            }
        }

Si l’on reprend nos Bindings de tout à l’heure :

   <TextBox Text="{Binding MaRacine.MonObjet.MaPropriete, Mode=TwoWay}"  />
   <TextBlock Text="{Binding MaRacine.MaProprieteEtoile}" />

Lorsque MaPropriété est changée, MaPropriétéEtoile est bien notifiée !

La notification ne se propage pas automatiquement à son parent. Elle doit être implémentée manuellement dans l’objet Parent, si nécessaire !

leave your comment