Zajmuję się tworzeniem aplikacji w Silverlight2 i próbuję podążać za wzorem Model-View-ViewModel. Wiążę właściwość IsEnabled w niektórych formantach z właściwością boolean w ViewModel.Powiadomienie PropertyChanged dla obliczonych właściwości
Występują problemy, gdy te właściwości pochodzą z innych właściwości. Załóżmy, że mam przycisk Zapisz, który chcę włączyć tylko wtedy, gdy możliwe jest zapisanie (dane zostały załadowane, a my obecnie nie jesteśmy zajęci robieniem danych w bazie danych).
Więc mam kilka właściwości takiego:
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");
}
}
}
Teraz to, co chcę zrobić to:
public bool CanSave
{
get { return this.IsLoaded && !this.DatabaseBusy; }
}
jednak uwagę na brak powiadomienia właściciela zmieniło.
Pytanie brzmi: Jaki jest czysty sposób na ujawnienie pojedynczej właściwości boolowskiej, do której mogę się przyłączyć, ale jest obliczany zamiast jawnie ustawionego i dostarcza powiadomienia, aby interfejs mógł być poprawnie aktualizowany?
EDIT:Dzięki za pomoc wszystkim - Mam idzie i miał iść na dokonanie atrybut niestandardowy. Zamieszczam tutaj źródło na wypadek, gdyby ktoś był zainteresowany. Jestem pewien, że można to zrobić w bardziej przejrzysty sposób, więc jeśli zauważysz jakieś błędy, dodaj komentarz lub odpowiedź.
Zasadniczo, co zrobiłem powstał interfejs, który zdefiniowaną listę par klucz-wartość trzymać jakie właściwości zależy od innych właściwości:
public interface INotifyDependentPropertyChanged
{
// key,value = parent_property_name, child_property_name, where child depends on parent.
List<KeyValuePair<string, string>> DependentPropertyList{get;}
}
Potem wykonanych atrybut, aby przejść na każdej nieruchomości:
[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));
}
}
}
}
Sam atrybut przechowuje tylko jeden ciąg znaków. Możesz zdefiniować wiele zależności dla każdej właściwości. Tuszem atrybutu jest funkcja statyczna BuildDependentPropertyList. Musisz zadzwonić do tego w konstruktorze swojej klasy. (Czy ktoś wie, czy można to zrobić za pomocą atrybutu class/constructor?) W moim przypadku wszystko to jest ukryte w klasie bazowej, więc w podklasach wystarczy umieścić atrybuty we właściwościach. Następnie modyfikujesz odpowiednik OnPropertyChanged, aby wyszukać zależności. Oto moja podstawowa klasa ViewModel jako przykład:
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);
}
}
Na koniec ustawiasz atrybuty na właściwościach, których dotyczy problem. Podoba mi się ten sposób, ponieważ właściwość pochodna przechowuje właściwości, od których zależy, a nie na odwrót.
[NotifyDependsOn("Session")]
[NotifyDependsOn("DatabaseBusy")]
public bool SaveEnabled
{
get { return !this.Session.IsLocked && !this.DatabaseBusy; }
}
Wielkim zastrzeżeniem jest to, że działa tylko wtedy, gdy inne nieruchomości są członkami obecnej klasy. W powyższym przykładzie, jeśli zmiana this.Session.IsLocked, powiadomienie nie zostanie wykonane. Sposób, w jaki to obejmuję, polega na zasubskrybowaniu tego Session.NotifyPropertyChanged i fire PropertyChanged dla "Session".(Tak, to doprowadziłoby do wydarzeń wypalania gdzie zrobił muszą)
Zamierzam przejść do nadpisania OnPropertyChanged. Zastanawiam się, czy zależności można zadeklarować jako atrybuty? – geofftnz
Możesz ustawić statyczną listę, aby śledzić nazwy właściwości, od których zależy CanSave. Wtedy byłby to pojedynczy "if (_canSaveProperties.Contains (propertyName))". –
Dobry pomysł, dzięki! – geofftnz