3

これまでのところ、私のモデルは を実装INotifyPropertyChangedしており、すべてのプロパティがこのイベントを発生させます。ほとんどすべての ViewModel は、PropertyChangedEventHandler.

問題は、プロパティの変更がビューにとって重要ではない場合でも、モデルのすべての変更に対してこのハンドラーが呼び出されることです。

1 つのオプションは、イベントを発生させたプロパティを確認することです。ただし、PropertyName 文字列をテストするという考えは好きではありません。次のような呼び出しを使用して、モデルで既に回避したプロパティ名のハードコーディングが必要ですPropertyChanged.Notify(()=> PropertyName)

私が見る 2 番目のオプションは、すべてのプロパティに単一のイベントを実装することです。

public event PropertyChangedEventHandler LayerChanged;
public event PropertyChangedEventHandler FieldChanged;
public event PropertyChangedEventHandler LinkDictionaryChanged;

....

ベストプラクティスは何ですか? 私は2番目のオプションを好むでしょう。

編集:私はより具体的にしようとします

私のモデルクラスは次のように機能します:

  public bool IsFeatureLayer
        {
            get { return _isFeatureLayer; }
            set { PropertyChanged.ChangeAndNotify(ref _isFeatureLayer, value, () => IsFeatureLayer);}
        }

または

  PropertyChanged.Notify(() => LinkDictionary);

したがって、問題は通知呼び出しをより安全にする方法ではありません。プロパティの文字列名なしでこれを行うために拡張メソッドを既に使用しているためです。

問題は、文字列を使用せずに誰がイベントを呼び出したかを調べる方法です。

 void _MCDAExtensionPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if(e.PropertyName.Equals("LinkDictionary"){
              //event handling
           }
        }

モデルのプロパティの名前が変更される可能性があり、別の場所で修正する必要があるため、これは完全に安全ではありません。

4

3 に答える 3

3

.NET 4.5 をターゲットにしている場合、新しい属性を使用すると、実装INotifyPropertyChangedがはるかに簡単かつ安全になります。CallerMemberName

つまり、このCallerMemberName属性を使用すると、呼び出し元のメンバーの名前をメソッド パラメーターとして取得できます。このようにして、次のようなものを持つことができます:

private string name;
public string Name
{
    get { return name; }
    set { SetProperty(ref name, value); }
}

private void SetProperty<T>(ref T field, T value, [CallerMemberName] string callerMemberName = "")
{
    // callerMemberName = "Name" (the property that called it).

    // Set the field value and raise PropertyChanged event.
}

ここで使用方法の例を見ることができます。

どのオプションを選択するかについては、プロパティごとに余分なイベントを発生させることで発生するコーディング オーバーヘッドとコードの混乱 (コード自体と intellisense の両方) とは対照的に、実行時間の違いは無視できると思います。私は間違いなく最初のオプションを選びます。

編集:

残念ながら、PropertyChangedイベントを処理する場合、文字列に対してしかテストできずPropertyName、プロパティ名が変更された場合でも一貫した方法でその文字列を取得する方法はありません。あなたが持っている依存関係プロパティの場合MyDependencyProperty.Name、それは通常のプロパティには当てはまりません。

最終的に、オプションは、プロパティごとに異なるイベントを使用するか、プロパティ名を保持するプロパティを定義するクラスで定数を定義することです。自分でハンドラーをアタッチする場所を実装する多くのクラスがないと仮定するとINotifyPropertyChanged、それらの特定のクラスの各プロパティのイベントを持つことはそれほど悪くありません。

于 2012-11-17T20:11:02.390 に答える
2

あなたの質問を正しく理解できれば、次のようなものを使用できます。

public static class PropertyChangedExtensions
{
    public static void RegisterPropertyHandler<T, TProperty>(this T obj, Expression<Func<T, TProperty>> propertyExpression, PropertyChangedEventHandler handlerDelegate)
        where T : class, INotifyPropertyChanged
    {
        if (obj == null) throw new ArgumentNullException("obj");

        var propertyName = GetPropertyName(propertyExpression);

        obj.PropertyChanged += (sender, args) =>
            {
                if (args.PropertyName == propertyName && handlerDelegate != null)
                    handlerDelegate(sender, args);
            };
    }

    public static void Notify<T>(this PropertyChangedEventHandler eventHandler, object sender, Expression<Func<T>> propertyExpression)
    {
        var handler = eventHandler;
        if (handler != null) handler(sender, new PropertyChangedEventArgs(GetPropertyName(propertyExpression)));
    }

    private static string GetPropertyName(LambdaExpression propertyExpression)
    {
        var memberExpression = propertyExpression.Body as MemberExpression;
        if (memberExpression == null)
        {
            var unaryExpression = propertyExpression.Body as UnaryExpression;
            if (unaryExpression == null) 
                throw new ArgumentException("Expression must be a UnaryExpression.", "propertyExpression");

            memberExpression = unaryExpression.Operand as MemberExpression;
        }

        if (memberExpression == null) 
            throw new ArgumentException("Expression must be a MemberExpression.", "propertyExpression");

        var propertyInfo = memberExpression.Member as PropertyInfo;
        if (propertyInfo == null) 
            throw new ArgumentException("Expression must be a Property.", "propertyExpression");

        return propertyInfo.Name;
    }
}

このRegisterPropertyHandlerメソッドを使用すると、「マジック ストリング」を使用せずに特定のプロパティのハンドラーを登録できます。次のように使用します。

public class PersonViewModel : INotifyPropertyChanged
{
    public PersonViewModel()
    {
        Address = new AddressViewModel();
        Address.RegisterPropertyHandler(a => a.ZipCode, ZipCodeChanged);
    }

    private AddressViewModel _address;

    public AddressViewModel Address
    {
        get { return _address; }
        set
        {
            _address = value;
            PropertyChanged.Notify(this, () => Address);
        }
    }

    private static void ZipCodeChanged(object sender, PropertyChangedEventArgs args)
    {
        // This will only be called when the 'ZipCode' property of 'Address' changes.
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public class AddressViewModel : INotifyPropertyChanged
{
    private string _zipCode;

    public string ZipCode
    {
        get
        {
            return _zipCode;
        }
        set
        {
            _zipCode = value;
            PropertyChanged.Notify(this, () => ZipCode);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

既にNotify拡張メソッドがあるようですので、追加する必要があるのはRegisterPropertyHandler. 少なくともこれは始まりです:)

于 2012-11-17T22:44:52.133 に答える
1

次のようなプロジェクト拡張メソッドにだけ:

    public static string GetPropertyName<TObj,TRet>(this TObj obj, Expression<Func<TObj,TRet>> expression)
    {
        MemberExpression body = GetMemberExpression(expression);
        return body.Member.Name;
    }

このようにして、ほとんどパフォーマンスを犠牲にして、プロパティ名、プロパティ名を含む文字列のコンパイルチェックを行うことができます。これにより、次のように呼び出すことができます。

PropertyChanged.Notify(this.GetPropetyName(t=>t.PropertyName))

理想的ではありませんが、文字列がなければそれを実現するのは困難です。

于 2012-11-17T18:59:55.760 に答える