2009-02-27 19 views
8

Silverlight2でアプリケーションを開発し、Model-View-ViewModelパターンに従おうとしています。いくつかのコントロールのIsEnabledプロパティをViewModelのbooleanプロパティにバインドしています。計算されたプロパティのPropertyChanged通知

これらのプロパティが他のプロパティから派生している場合、問題が発生しています。 Saveボタンがあるとしましょう。保存が可能なときにのみ有効にしたい(データがロードされていて、現在データベース内のものを処理中ではありません)。

だから私はこのような性質をいくつ持っている:

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

しかし、プロパティ変更通知の欠如に注意してください。今、私は何をしたいのか

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"); 
      } 
     } 
    } 

はこれです。

私はバインドできる単一のブール値プロパティを公開する明確な方法ですが、明示的に設定するのではなく計算され、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; } 
    } 

ここで大きな注意点は、他のプロパティが現在のクラスのメンバーである場合のみ動作することです。上記の例では、このSession.Session.IsLockedが変更された場合、通知は機能しません。私がこれを回避する方法はthis.Session.NotifyPropertyChangedを購読し、 "Session"のPropertyChangedを起動することです。(はい、これは彼らがする必要があるdidntはどこ発射事象につながる)

答えて

6

これを行うには伝統的な方法は、このように、あなたの計算された1に影響を与える可能性の各プロパティのOnPropertyChangedを呼び出しを追加することです:

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

これは、例えば、もし(少し厄介取得することができ、 CanSaveの変更による計算)。

つこれを回避する方法は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"); 
    } 
} 

次に、IsLoadedとDatabaseBusyのセッターでUpdateCanSave()を呼び出しますか?

異なるクラスにあるため、IsLoadedとDatabaseBusyのセッターを変更できない場合は、IsLoadedとDatabaseBusyを定義するオブジェクトのPropertyChangedイベントハンドラーでUpdateCanSave()を呼び出すことができます。

関連する問題