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; }
}
ただし、プロパティ変更通知がないことに注意してください。
問題は次のとおりです。バインドできる単一のブール値プロパティを公開するクリーンな方法は何ですか?ただし、明示的に設定する代わりに計算され、UI が正しく更新されるように通知を提供します。
編集: みんなの助けに感謝します-私はそれを始めて、カスタム属性を作成してみました. 誰かが興味を持っている場合に備えて、ここにソースを投稿します。よりクリーンな方法で実行できると確信しているので、欠陥が見つかった場合は、コメントまたは回答を追加してください。
基本的に私がしたことは、どのプロパティが他のプロパティに依存するかを保持するキーと値のペアのリストを定義するインターフェイスを作成することでした:
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));
}
}
}
}
属性自体には、1 つの文字列のみが格納されます。プロパティごとに複数の依存関係を定義できます。属性の根幹は BuildDependentPropertyList 静的関数にあります。クラスのコンストラクターでこれを呼び出す必要があります。(クラス/コンストラクター属性を介してこれを行う方法があるかどうか知っている人はいますか?)私の場合、これはすべて基本クラスに隠されているため、サブクラスではプロパティに属性を配置するだけです。次に、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 にサブスクライブし、「Session」の PropertyChanged を起動することです。(はい、これにより、必要のない場所でイベントが発生します)