Я разрабатываю приложение в 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 для «Сессии».(Да, это привело бы к событиям стрельбы, где они не должны)
Я собираюсь перейти к переопределению OnPropertyChanged. Интересно, могут ли зависимости быть объявлены как атрибуты? – geofftnz
Вы можете настроить статический список, чтобы отслеживать имена свойств, от которых зависит CanSave. Тогда это будет единственный «if (_canSaveProperties.Contains (propertyName)»). –
Хорошая идея, спасибо! – geofftnz