piątek, 15 czerwca 2012

Jak sterować wyświetlaniem danych na widoku? Rozwiązaniem jest...

Kontynuując wątek "bindowania", chciałem wytłumaczyć w jaki sposób z klasy "ViewModel" przypisanej do DataContext możemy wpływać na zmianę na widoku.
Należy wyjaśnić tę kwestię gdyż jest ona bardzo istotna i będę jej używał podczas dalszego opisywania moich bojów z "Binding" :).
Cofnijmy się więc do definicji klasy "ViewModel'u".

    public class MainWindowViewModel
    {
        private string _name = string.Empty;
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
    }
Gdy właściwość "Name" zdefiniowana jak powyżej, zostanie zmieniona zmiana ta nie będzie odwzorowana na widoku.
Co w takim razie należy zrobić?
Przecież właśnie o to chodzi, żeby zmiany w "ViewModel'u" były odwzorowane na widoku!!!
Nie ma się co denerwować, rozwiązanie oczywiście jest. :)
Należy ogłosić wszem i wobec, że wartość właściwości "Name" została zmieniona, użyjemy do tego interfejsu "INotifyPropertyChanged".
W swoim kodzie używam klasy bazowej implementującej ten interfejs, po której dziedziczę klasy wymagające jego implementacji.

Zaczynam od implementacji zdarzenia "PropertyChanged" z interfejsu.
Poniższa implementacja odporna jest na wywołania z różnych wątków, zapewnia nam to "lock".
        object _objectLock = new object();
        event PropertyChangedEventHandler PropertyChangedEvent;
        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                lock (_objectLock)
                {
                    PropertyChangedEvent += value;
                }
            }
            remove
            {
                lock (_objectLock)
                {
                    PropertyChangedEvent -= value;
                }
            }
        }
        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler handler;
            lock (_objectLock)
            {
                handler = PropertyChangedEvent;
            }
            if (handler != null)
                handler(this, e);
        }
Następnie definiujemy metodę, która ma być wywoływana po zmianie wartości właściwości.
        protected void FirePropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
Modyfikacja właściwości "Name" z wywołaniem zdarzenia wygląda tak:
        public string Name
        {
            get { return _name; }
            set 
            { 
                _name = value;
                FirePropertyChanged("Name");
            }
        }
Niestety takie wywołanie nie jest odporne na zmianę nazwy właściwości "Name", dla mnie dodatkowo jest mało estetyczne.
Dużo wygodniej było by przekazać jako parametr samą właściwość "Name".
        public string Name
        {
            get { return _name; }
            set 
            { 
                _name = value;
                FirePropertyChanged(() => Name);
            }
        }
Aby uzyskać możliwość takiego wywoływanie zdarzenia należy dodać metodę:
        protected void FirePropertyChanged<T>(Expression<Func<T>> propertyExpresssion)
        {
            FirePropertyChanged(Helper.GetPropertyName(propertyExpresssion));
        }
Dodatkowo należy też dodać metodę "GetPropertyName" przekształcającą wyrażenie zawierające instancję właściwości w nazwę tej właściwości.
Oczywiście i takie rozwiązanie dla was przygotowałem :D
            public static string GetPropertyName<T>(Expression<Func<T>> propertyExpresssion)
            {
                if (propertyExpresssion == null)
                    throw new ArgumentNullException("propertyExpresssion");

                var memberExpression = propertyExpresssion.Body as MemberExpression;
                if (memberExpression == null)
                    throw new ArgumentException("The expression is not a member access expression.");

                var property = memberExpression.Member as PropertyInfo;
                if (property == null)
                    throw new ArgumentException("The member access expression does not access a property.");

                var getMethod = property.GetGetMethod(true);
                if (getMethod.IsStatic)
                    throw new ArgumentException("The referenced property is a static property.");

                return memberExpression.Member.Name;
            }
Podsumowując cały wpis, zamieszczam pełen kod klasy "ObservableItem":
    public class ObservableItem : INotifyPropertyChanged
    {
        object _objectLock = new object();
        event PropertyChangedEventHandler PropertyChangedEvent;
        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                lock (_objectLock)
                {
                    PropertyChangedEvent += value;
                }
            }
            remove
            {
                lock (_objectLock)
                {
                    PropertyChangedEvent -= value;
                }
            }
        }
        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler handler;
            lock (_objectLock)
            {
                handler = PropertyChangedEvent;
            }
            if (handler != null)
                handler(this, e);
        }
        protected void FirePropertyChanged<T>(Expression<Func<T>> propertyExpresssion)
        {
            FirePropertyChanged(Helper.GetPropertyName(propertyExpresssion));
        }
        protected void FirePropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
        public static class Helper
        {
            public static string GetPropertyName<T>(Expression<Func<T>> propertyExpresssion)
            {
                if (propertyExpresssion == null)
                    throw new ArgumentNullException("propertyExpresssion");

                var memberExpression = propertyExpresssion.Body as MemberExpression;
                if (memberExpression == null)
                    throw new ArgumentException("The expression is not a member access expression.");

                var property = memberExpression.Member as PropertyInfo;
                if (property == null)
                    throw new ArgumentException("The member access expression does not access a property.");

                var getMethod = property.GetGetMethod(true);
                if (getMethod.IsStatic)
                    throw new ArgumentException("The referenced property is a static property.");

                return memberExpression.Member.Name;
            }
        }
    }

Jeszcze chciałem wspomnieć, że jeśli potrzebujemy kolekcję i chcielibyśmy obserwować kiedy liczba jej elementów zostanie zmieniona to najlepszym rozwiązaniem jest użycie "ObservableCollection".

Brak komentarzy:

Prześlij komentarz