72 votes

Lancer un événement à chaque fois que la valeur d'une propriété est modifiée ?

Il y a une propriété, elle s'appelle ImageFullPath1

public string ImageFullPath1 {get; set; }

Je vais déclencher un événement chaque fois que sa valeur change. Je suis conscient de la modification INotifyPropertyChanged mais je veux le faire avec des événements.

176voto

Aaronaught Points 73049

Le site INotifyPropertyChanged interface est mis en œuvre avec des événements. L'interface n'a qu'un seul membre, PropertyChanged qui est un événement auquel les consommateurs peuvent s'abonner.

La version que Richard a postée n'est pas sûre. Voici comment implémenter cette interface en toute sécurité :

public class MyClass : INotifyPropertyChanged
{
    private string imageFullPath;

    protected void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, e);
    }

    protected void OnPropertyChanged(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    public string ImageFullPath
    {
        get { return imageFullPath; }
        set
        {
            if (value != imageFullPath)
            {
                imageFullPath = value;
                OnPropertyChanged("ImageFullPath");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Notez que cela fait les choses suivantes :

  • Abstraite les méthodes de notification de changement de propriété afin que vous puissiez facilement l'appliquer à d'autres propriétés ;

  • Fait une copie de la PropertyChanged délégué avant en essayant de l'invoquer (si vous ne le faites pas, cela créera une situation de concurrence).

  • Met correctement en œuvre le INotifyPropertyChanged interface.

Si vous voulez en outre, créer une notification pour un spécifique en cours de modification, vous pouvez ajouter le code suivant :

protected void OnImageFullPathChanged(EventArgs e)
{
    EventHandler handler = ImageFullPathChanged;
    if (handler != null)
        handler(this, e);
}

public event EventHandler ImageFullPathChanged;

Ajoutez ensuite la ligne OnImageFullPathChanged(EventArgs.Empty) après la ligne OnPropertyChanged("ImageFullPath") .

Puisque nous avons .Net 4.5, il existe l'option CallerMemberAttribute qui permet de se débarrasser de la chaîne codée en dur pour le nom de la propriété dans le code source :

    protected void OnPropertyChanged(
        [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    public string ImageFullPath
    {
        get { return imageFullPath; }
        set
        {
            if (value != imageFullPath)
            {
                imageFullPath = value;
                OnPropertyChanged();
            }
        }
    }

29 votes

+1 pour être la seule personne dans ce fil de discussion jusqu'à présent à obtenir le contrôle nul sur l'événement correct.

0 votes

@Aaronaught : Comment câbler l'event-method avec l'event?pouvez-vous s'il vous plaît expliquer Je veux dire, où dois-je écrire l'implémentation pour l'événement ?

0 votes

Pourquoi utiliser la var locale "handler", pourquoi ne pas simplement if (ImageFullPathChanged != null) ImageFullPathChanged(this, e) ;

38voto

Mikael Sundberg Points 1949

J'utilise en grande partie les mêmes modèles qu'Aaronaught, mais si vous avez beaucoup de propriétés, il peut être intéressant d'utiliser une petite méthode magique générique pour rendre votre code un peu plus SEC

public class TheClass : INotifyPropertyChanged {
    private int _property1;
    private string _property2;
    private double _property3;

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {
        PropertyChangedEventHandler handler = PropertyChanged;
        if(handler != null) {
            handler(this, e);
        }
    }

    protected void SetPropertyField<T>(string propertyName, ref T field, T newValue) {
        if(!EqualityComparer<T>.Default.Equals(field, newValue)) {
            field = newValue;
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
    }

    public int Property1 {
        get { return _property1; }
        set { SetPropertyField("Property1", ref _property1, value); }
    }
    public string Property2 {
        get { return _property2; }
        set { SetPropertyField("Property2", ref _property2, value); }
    }
    public double Property3 {
        get { return _property3; }
        set { SetPropertyField("Property3", ref _property3, value); }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

En général, je fais également en sorte que la méthode OnPropertyChanged soit virtuelle pour permettre aux sous-classes de la surcharger afin d'attraper les changements de propriété.

12 votes

Maintenant, avec .NET 4.5, vous pouvez même obtenir le nom de la propriété gratuitement en utilisant l'attribut CallerMemberNameAttribute. msdn.microsoft.com/fr/us/library/ .

0 votes

Cela fonctionne très bien. Merci pour l'exemple. +1 pour le CallerMemberName

1 votes

Le temps a passé depuis la dernière édition et certaines choses ont changé. Peut-être une meilleure façon d'invoquer le délégué d'événement : PropertyChanged?.Invoke(this, e);

9voto

Ryan Brunner Points 8983

Le déclenchement d'un événement lorsqu'une propriété est modifiée est précisément ce que fait INotifyPropertyChanged. L'implémentation de INotifyPropertyChanged ne nécessite qu'un seul membre, à savoir l'événement PropertyChanged. Tout ce que vous implémenterez vous-même sera probablement identique à cette implémentation, il n'y a donc aucun avantage à ne pas l'utiliser.

2 votes

+1 pour la vérité. Même si vous voulez implémenter un système séparé XChangedEvent pour chaque propriété, vous faites déjà le travail, alors allez-y et implémentez aussi INotifyPropertyChanged. L'avenir (comme WPF) vous en remerciera, car c'est ce que l'avenir attend de vous.

5voto

Richard Berg Points 14218
public event EventHandler ImageFullPath1Changed;

public string ImageFullPath1
{
    get
    {
        // insert getter logic
    }
    set
    {
        // insert setter logic       

        // EDIT -- this example is not thread safe -- do not use in production code
        if (ImageFullPath1Changed != null && value != _backingField)
            ImageFullPath1Changed(this, new EventArgs(/*whatever*/);
    }
}                        

Cela dit, je suis tout à fait d'accord avec Ryan. Ce scénario est précisément la raison pour laquelle INotifyPropertyChanged existe.

3 votes

Cette invocation d'événement a une condition de course. La valeur de ImageFullPath1Changed peut se transformer en null entre le contrôle et l'invocation suivante. N'invoquez pas d'événements de cette manière !

2 votes

Votre contrôle de nullité de l'événement ImageFullPath1Changed n'est pas sûr. Étant donné que les événements peuvent être souscrits/désouscrits de manière asynchrone à partir de l'extérieur de votre classe, il pourrait devenir nul après votre vérification de nullité et provoquer une NullReferenceException. Au lieu de cela, vous devriez prendre une copie locale avant de vérifier la nullité. Voir la réponse d'Aaronaught.

4voto

Oded Points 271275

Si vous modifiez votre propriété pour utiliser un champ de sauvegarde (au lieu d'une propriété automatique), vous pouvez procéder comme suit :

public event EventHandler ImageFullPath1Changed;
private string _imageFullPath1 = string.Empty;

public string ImageFullPath1 
{
  get
  {
    return imageFullPath1 ;
  }
  set
  {
    if (_imageFullPath1 != value)
    { 
      _imageFullPath1 = value;

      EventHandler handler = ImageFullPathChanged;
      if (handler != null)
        handler(this, e);
    }
  }
}

0 votes

Votre vérification de la nullité de l'événement ImageFullPath1Changed n'est pas sûre. Étant donné que les événements peuvent être souscrits/désouscrits de manière asynchrone à partir de l'extérieur de votre classe, il pourrait devenir nul après votre vérification de nullité et provoquer une NullReferenceException. Au lieu de cela, vous devriez prendre une copie locale avant de vérifier la nullité. Voir la réponse d'Aaronaught

1 votes

@Simon P Stevens - merci pour l'information. Réponse mise à jour pour refléter.

0 votes

@Oded J'ai essayé d'utiliser votre approche, mais pour le code ci-dessus handler(this, e), e does not exist in current context Est-ce que je me trompe ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X