O interfejsie INotifyPropertyChanged słyszał każdy, gdy napotkał wzorzec MVVM. Niestety podstawowy sposób tworzenia właściwości w klasie ViewModel jest zbyt czasochłonny.
Zacznijmy od przykładu:

    public class BasicViewModel : INotifyPropertyChanged
    {
        private string customerName;

        public event PropertyChangedEventHandler PropertyChanged;

        public string CustomerName
        {
            get
            {
                return customerName;
            }

            set
            {
                if (value != customerName)
                {
                    customerName = value;
                    NotifyPropertyChanged();
                }
            }
        }

        public BasicViewModel()
        {
            CustomerName = "Jan Kowalski";
        }

        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

Nie dość, że ciało każdej z właściwości jest dość rozległe, to należy implementować interfejs INotifyPropertyChanged w każdej z klas ViewModel osobno. Na szczęście można to uprościć w poniższy sposób:

public class BetterViewModel : ViewModelBase
{
    public string CustomerName { get => Get<string>(); set => Set(value); }

    public BetterViewModel()
    {
        CustomerName = "Andrzej Nowak";
    }
}

Jak widać, kod jest dużo bardziej kompaktowy.
Mechanizm data-binding’u został przeniesiony do klasy bazowej. Zamiast prywatnych pól dane trzymane są w słowniku, dostęp do niego odbywa się przy użyciu atrybutu CallerMemberName. Rozwiązanie działa dla typów prostych oraz referencyjnych.

    public class ViewModelBase : INotifyPropertyChanged
    {
        private readonly Dictionary<string, object> properties
            = new Dictionary<string, object>();

        public event PropertyChangedEventHandler PropertyChanged;

        protected T Get<T>([CallerMemberName] string name = null)
        {
            object value = null;
            if (properties.TryGetValue(name, out value))
                return value == null ? default(T) : (T)value;
            return default(T);
        }

        protected void Set<T>(T value, [CallerMemberName] string name = null)
        {
            if (Equals(value, Get<T>(name)))
                return;
            properties[name] = value;
            OnPropertyChanged(name);
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

To nie koniec możliwości, jakie daje nam takie rozwiązanie.
Poniżej kilka pomysłów:

  • automatyczny trim dla pól typu string
  • walidacja (polecam FluentValidation)
  • sterowanie pracą przycisku Zapisz na formularzu poprzez zmianę stanu dodatkowego pola IsDirty, jeśli zmianie ulegnie dowolne inne pole

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *