Я разрабатываю приложение в Silverlight2 и пытаюсь следовать шаблону Model-View-ViewModel. Я связываю свойство IsEnabled в некоторых элементах управления с логическим свойством в ViewModel.
У меня возникают проблемы, когда эти свойства получены из других свойств. Допустим, у меня есть кнопка «Сохранить», которую я хочу включить только тогда, когда это возможно сохранить (данные загружены, и в настоящее время мы не заняты работой с базами данных).
Итак, у меня есть пара таких свойств:
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; }
}
Но обратите внимание на отсутствие уведомления об изменении свойства.
Таким образом, вопрос заключается в следующем: как правильно выставить единственное логическое свойство, к которому я могу привязаться, но оно вычисляется вместо явной установки и предоставляет уведомление, чтобы пользовательский интерфейс мог корректно обновляться?
РЕДАКТИРОВАТЬ: Спасибо за помощь всем - я получил его и попытался создать пользовательский атрибут. Я публикую здесь источник на тот случай, если кому-то будет интересно. Я уверен, что это можно сделать более чистым способом, поэтому, если вы видите какие-либо недостатки, добавьте комментарий или ответ.
По сути, я сделал интерфейс, который определил список пар ключ-значение для хранения свойств, зависящих от других свойств:
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 изменяется, уведомление не проходит. Чтобы обойти это, нужно подписаться на this.Session.NotifyPropertyChanged и запустить PropertyChanged для «Session». (Да, это приведет к тому, что события будут запускаться там, где это не нужно)