2009-02-27 3 views
8

Я разрабатываю приложение в Silverlight2 и пытаюсь следовать шаблону Model-View-ViewModel. Я привязываю свойство IsEnabled к некоторым элементам управления к логическому свойству в ViewModel.Уведомление PropertyChanged для вычисленных свойств

У меня возникают проблемы, когда эти свойства получены из других свойств. Предположим, у меня есть кнопка «Сохранить», которую я хочу включить только тогда, когда можно сохранить (данные были загружены, а мы в настоящее время не заняты работой в базе данных).

Так у меня есть несколько свойств, как это:

private bool m_DatabaseBusy; 
    public bool DatabaseBusy 
    { 
     get { return m_DatabaseBusy; } 
     set 
     { 
      if (m_DatabaseBusy != value) 
      { 
       m_DatabaseBusy = value; 
       OnPropertyChanged("DatabaseBusy"); 
      } 
     } 
    } 

    private bool m_IsLoaded; 
    public bool IsLoaded 
    { 
     get { return m_IsLoaded; } 
     set 
     { 
      if (m_IsLoaded != value) 
      { 
       m_IsLoaded = value; 
       OnPropertyChanged("IsLoaded"); 
      } 
     } 
    } 

Теперь то, что я хочу сделать это:

public bool CanSave 
{ 
    get { return this.IsLoaded && !this.DatabaseBusy; } 
} 

Но обратите внимание на отсутствие уведомления изменения свойства.

Итак, вопрос заключается в следующем: что представляет собой чистый способ разоблачения одного логического свойства, к которому я могу привязываться, но рассчитывается вместо того, чтобы быть явно установленным и предоставляет уведомление, чтобы пользовательский интерфейс мог корректно обновлять?

EDIT:Спасибо за помощь всем - я получил это и пошел на создание пользовательского атрибута. Я размещаю источник здесь, если кому-то это интересно. Я уверен, что это можно сделать чище, поэтому, если вы видите какие-то недостатки, добавьте комментарий или ответ.

В основном то, что я сделал, это сделал интерфейс, определенный список пар ключ-значение, чтобы держать то, что свойства зависят от других свойств:

public interface INotifyDependentPropertyChanged 
{ 
    // key,value = parent_property_name, child_property_name, where child depends on parent. 
    List<KeyValuePair<string, string>> DependentPropertyList{get;} 
} 

Я тогда сделал атрибут для перехода на каждое свойство:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)] 
public class NotifyDependsOnAttribute : Attribute 
{ 
    public string DependsOn { get; set; } 
    public NotifyDependsOnAttribute(string dependsOn) 
    { 
     this.DependsOn = dependsOn; 
    } 

    public static void BuildDependentPropertyList(object obj) 
    { 
     if (obj == null) 
     { 
      throw new ArgumentNullException("obj"); 
     } 

     var obj_interface = (obj as INotifyDependentPropertyChanged); 

     if (obj_interface == null) 
     { 
      throw new Exception(string.Format("Type {0} does not implement INotifyDependentPropertyChanged.",obj.GetType().Name)); 
     } 

     obj_interface.DependentPropertyList.Clear(); 

     // Build the list of dependent properties. 
     foreach (var property in obj.GetType().GetProperties()) 
     { 
      // Find all of our attributes (may be multiple). 
      var attributeArray = (NotifyDependsOnAttribute[])property.GetCustomAttributes(typeof(NotifyDependsOnAttribute), false); 

      foreach (var attribute in attributeArray) 
      { 
       obj_interface.DependentPropertyList.Add(new KeyValuePair<string, string>(attribute.DependsOn, property.Name)); 
      } 
     } 
    } 
} 

В самом атрибуте хранится только одна строка. Вы можете определить несколько зависимостей для каждого свойства. Гитара атрибута находится в статической функции BuildDependentPropertyList. Вы должны вызвать это в конструкторе своего класса. (Кто-нибудь знает, есть ли способ сделать это с помощью атрибута class/constructor?) В моем случае все это скрыто в базовом классе, поэтому в подклассах вы просто поместите атрибуты в свойства. Затем вы изменяете эквивалент OnPropertyChanged для поиска любых зависимостей. Вот мой базовый класс ViewModel в качестве примера:

public class ViewModel : INotifyPropertyChanged, INotifyDependentPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    protected virtual void OnPropertyChanged(string propertyname) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(propertyname)); 

      // fire for dependent properties 
      foreach (var p in this.DependentPropertyList.Where((x) => x.Key.Equals(propertyname))) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs(p.Value)); 
      } 
     } 
    } 

    private List<KeyValuePair<string, string>> m_DependentPropertyList = new List<KeyValuePair<string, string>>(); 
    public List<KeyValuePair<string, string>> DependentPropertyList 
    { 
     get { return m_DependentPropertyList; } 
    } 

    public ViewModel() 
    { 
     NotifyDependsOnAttribute.BuildDependentPropertyList(this); 
    } 
} 

Наконец, вы устанавливаете атрибуты для затронутых свойств. Мне нравится этот путь, потому что производное свойство содержит свойства, от которых он зависит, а не наоборот.

[NotifyDependsOn("Session")] 
    [NotifyDependsOn("DatabaseBusy")] 
    public bool SaveEnabled 
    { 
     get { return !this.Session.IsLocked && !this.DatabaseBusy; } 
    } 

Большое предупреждение здесь заключается в том, что он работает только в том случае, если другие свойства являются членами текущего класса. В приведенном выше примере, если this.Session.IsLocked изменяется, уведомление не проходит. Способ, которым я обойду это, - подписаться на это .Session.NotifyPropertyChanged и запустить PropertyChanged для «Сессии».(Да, это привело бы к событиям стрельбы, где они не должны)

ответ

6

Традиционный способ сделать это, чтобы добавить OnPropertyChanged вызов каждому из свойств, которые могут повлиять на ваш рассчитывается один, как это:

public bool IsLoaded 
{ 
    get { return m_IsLoaded; } 
    set 
    { 
     if (m_IsLoaded != value) 
     { 
      m_IsLoaded = value; 
      OnPropertyChanged("IsLoaded"); 
      OnPropertyChanged("CanSave"); 
     } 
    } 
} 

Это может получить немного неаккуратно (если, например, ваш расчет в изменениях CanSave).

One (уборщик я не знаю?) Способ обойти это было бы переопределить OnPropertyChanged и сделать вызов там:

protected override void OnPropertyChanged(string propertyName) 
{ 
    base.OnPropertyChanged(propertyName); 
    if (propertyName == "IsLoaded" /* || propertyName == etc */) 
    { 
     base.OnPropertyChanged("CanSave"); 
    } 
} 
+0

Я собираюсь перейти к переопределению OnPropertyChanged. Интересно, могут ли зависимости быть объявлены как атрибуты? – geofftnz

+0

Вы можете настроить статический список , чтобы отслеживать имена свойств, от которых зависит CanSave. Тогда это будет единственный «if (_canSaveProperties.Contains (propertyName)»). –

+0

Хорошая идея, спасибо! – geofftnz

2

Вы должны добавить уведомление для изменения свойств CanSave везде одно из свойств зависит изменения:

OnPropertyChanged("DatabaseBusy"); 
OnPropertyChanged("CanSave"); 

И

OnPropertyChanged("IsEnabled"); 
OnPropertyChanged("CanSave"); 
+0

Это будет работать правильно, но это означает, что вы будете недействительности CanSave больше, чем это необходимо, возможно, чтобы остальная часть приложения выполняла больше работы, чем требуется. – KeithMahoney

+0

Что делать, если у меня нет доступа к установленному методу свойства? – geofftnz

1

Как насчет этого решения?

private bool _previousCanSave; 
private void UpdateCanSave() 
{ 
    if (CanSave != _previousCanSave) 
    { 
     _previousCanSave = CanSave; 
     OnPropertyChanged("CanSave"); 
    } 
} 

Затем вызвать UpdateCanSave() в сеттерах IsLoaded и DatabaseBusy?

Если вы не можете изменять настройки IsLoaded и DatabaseBusy, потому что они находятся в разных классах, вы можете попробовать вызвать UpdateCanSave() в обработчике события PropertyChanged для объекта, определяющего IsLoaded и DatabaseBusy.

Смежные вопросы