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