Kategorie szkoleń | Egzaminy | Kontakt

Odpowiedzi (2)

  • 7

To interfejs używany podczas DataBindingu, czyli łączenia wizualnych kontrolek z właściwościami obiektów. Stosowany jest w technologiach WinForms oraz WPF.

Wyobraźmy sobie, że mamy kontrolkę TextBox oraz obiekt klasy Customer z właściwością CustomerName. Kontrolka jest powiązania za pomocą DataBindingu z właściwością obiektu:


<TextBox Text="{Binding Path=Customer.CustomerName}" />



Dzięki temu tekst wprowadzany do kontrolki jest przenoszony do właściwości obiektu.

Niestety, w drugą stronę to nie działa. Jeśli zmieni się wartość w obiekcie, na przykład gdzieś w logice aplikacji, to kontrolka ta nadal będzie wyświetlała poprzednią wartość, gdyż kontrolka nie wie, że zmieniła się wartość w obiekcie.

Musimy odświeżyć kontrolkę. Jednak takie ręczne odświeżanie kontrolek nie byłoby zbyt wygodne i wydajne.

Dlatego z pomocą przychodzi interfejs INotifyPropertyChanged, który posiada zdarzenie PropertyChanged. Wystarczy wywołać to zdarzenie z naszej klasy, a kontrolka automatycznie się odświeży! 

Jedyne, co musimy zrobić, to zaimplementować ten interfejs. Interfejs jest bardzo prosty - zawiera tylko jedno zdarzenie PropertyChanged.

Implementację tego interfejsu najlepiej od razu umieścić w abstrakcyjnej klasie bazowej, aby można go było używać w całym projekcie.

Przykładowa implementacja:


using System.ComponentModel;

namespace Sample
{
  public abstract class Base : INotifyPropertyChanged
  {
      public event PropertyChangedEventHandler PropertyChanged;


      // Create the OnPropertyChanged method to raise the event 
      protected void OnPropertyChanged(string name)
      {
          if (PropertyChanged != null)
          {
              PropertyChanged(this, new PropertyChangedEventArgs(name));
          }
      }
  }
}

 

Teraz możemy go użyć:


  public class Customer : Base
  {

      private string customerName;
      

      public string CustomerName
      {
          get { return name; }
          set
          {
              name = value;
              OnPropertyChanged("CustomerName");
          }
      }

}

 

Jak widać, przy każdej zmianie generowane jest zdarzenie z nazwą właściwości. Dzięki temu wszystkie kontrolki, które zbindowane są do właściwości o tej nazwie, odświeżą się.

To najprostsza implementacja, ale posiada jeden zasadniczy mankament - przy wywołaniu zdarzenia przekazujemy nazwę właściwości w ciągu tekstowego. Łatwo popełnić błąd w postaci literówki. Dodatkowo trzeba pamiętać, aby przy zmianie nazwy właściwości poprawić również wywołanie.

Rozwiązaniem tego problemu jest zastosowanie wyrażeń Lambda:
 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Linq.Expressions;

namespace Sulmar.AQuick.Model
{
    public abstract class Base : INotifyPropertyChanged
    {

        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected string GetPropertyName<TProperty>(Expression<Func<TProperty>> property)
        {
            var lambda = (LambdaExpression)property;

            MemberExpression memberExpression;
            if (lambda.Body is UnaryExpression)
            {
                var unaryExpression = (UnaryExpression)lambda.Body;
                memberExpression = (MemberExpression)unaryExpression.Operand;
            }
            else
                memberExpression = (MemberExpression)lambda.Body;

            return memberExpression.Member.Name;
        }

        public void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
        {
            OnPropertyChanged(GetPropertyName(property));
        }

    }
}

 

Teraz możemy przekazać nazwę właściwości za pomocą wyrażenia Lambda:


 public class Customer : Base
  {

      private string customerName;
      

      public string CustomerName
      {
          get { return name; }
          set
          {
              name = value;
              OnPropertyChanged( () => CustomerName);
          }
      }

}

 

Ewentualną pomyłkę w nazwie wykryje teraz kompilator.

W przypadku, gdy korzystamy z .NET 4.5, możemy to napisać jeszcze inaczej i prościej:

 

using System.ComponentModel;

namespace Sample
{
  public abstract class Base : INotifyPropertyChanged
  {
      public event PropertyChangedEventHandler PropertyChanged;


      // Create the OnPropertyChanged method to raise the event 
      protected void OnPropertyChanged([CallerMemberName] string name="")
      {
          if (PropertyChanged != null)
          {
              PropertyChanged(this, new PropertyChangedEventArgs(name));
          }
      }
  }
}


 

Użycie wygląda następująco:


  public class Customer : Base
  {

      private string customerName;
      

      public string CustomerName
      {
          get { return name; }
          set
          {
              name = value;
              OnPropertyChanged();
          }
      }

}

 

Jak widać, nie musimy już przekazywać nazwy właściwości. Nazwa właściwości jest pobierana za pomocą atrybutu CallerMemberName, który posiada nazwę metody/właściwości, skąd przychodzi wywołanie.

 

Reasumując, interfejs INotifyPropertyChanged odgrywa kluczową rolę w DataBindingu. Powinien znać go i rozumieć każdy programista WPF lub WinForms, która stosuje DataBinding. Implementuje się go tylko raz, ale używa wielokrotnie.

 

 

 

  • Odpowiedział
  • @ | 04.08.2014
  • TRENER ALTKOM AKADEMII
Komentarze
  • 3

W C# 6.0 można to zaimplementować jeszcze w inny sposób za pomocą nowego operatora nameof. Operator nameof zwraca nazwę zmiennej.

 

Implementacja interfejsu z użyciem operatora nameof:


using System.ComponentModel;

namespace Sample
{
  public abstract class Base : INotifyPropertyChanged
  {
      public event PropertyChangedEventHandler PropertyChanged;


      // Create the OnPropertyChanged method to raise the event 
      protected void OnPropertyChanged([CallerMemberName] string name)
      {
          if (PropertyChanged != null)
          {
              PropertyChanged(this, new PropertyChangedEventArgs(name));
          }
      }
  }
}



Sposób użycia:


    public class Customer : Base
    {
        private string _customerName;

        public string CustomerName
        {
            get { return _customerName ?? ""; }
            set
            {
                if (_customerName != value)
                {
                    _customerName = value;
                    OnPropertyChanged(nameof(CustomerName));
                };
            }
        }
    }

 

Przypomina to bardzo pierwszy i najprostszy sposób implementacji. Jednak dzięki zastosowaniu operatora nameof nie ma niebezpieczeństwa, że popełnimy literówkę lub zmienimy nazwę właściwości. Jednak użycie tego nie jest już tak wygodnie jak sposób z CallerMemberName, w którym nie musimy przekazywać nazwy.

  • Odpowiedział
  • @ | 17.06.2015
  • TRENER ALTKOM AKADEMII