8

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 を起動することです。(はい、これにより、必要のない場所でイベントが発生します)

4

3 に答える 3

6

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

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

これは少し厄介になる可能性があります (たとえば、CanSave での計算が変更された場合)。

これを回避する1つの(よりクリーンな?わかりません)方法は、OnPropertyChangedをオーバーライドして、そこで呼び出しを行うことです。

protected override void OnPropertyChanged(string propertyName)
{
    base.OnPropertyChanged(propertyName);
    if (propertyName == "IsLoaded" /* || propertyName == etc */)
    {
        base.OnPropertyChanged("CanSave");
    }
}
于 2009-02-27T02:33:01.380 に答える
2

依存するプロパティの 1 つが変更されるたびに、CanSave プロパティの変更の通知を追加する必要があります。

OnPropertyChanged("DatabaseBusy");
OnPropertyChanged("CanSave");

OnPropertyChanged("IsEnabled");
OnPropertyChanged("CanSave");
于 2009-02-27T02:31:58.497 に答える
1

この解決策はどうですか?

private bool _previousCanSave;
private void UpdateCanSave()
{
    if (CanSave != _previousCanSave)
    {
        _previousCanSave = CanSave;
        OnPropertyChanged("CanSave");
    }
}

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

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

于 2009-02-27T02:32:15.047 に答える