【问题标题】:Should I create a lot of PropertyChangedEventHandler or test the PropertyChangedEventArgs?我应该创建很多 PropertyChangedEventHandler 还是测试 PropertyChangedEventArgs?
【发布时间】:2012-11-06 04:25:36
【问题描述】:

到目前为止,我的模型实现了INotifyPropertyChanged,并且每个属性都会引发此事件。 几乎所有 ViewModel 都通过 PropertyChangedEventHandler 监听这些变化。

问题在于,模型中的每次更改都会调用此处理程序,即使属性更改对视图并不重要。

一种选择是检查哪个属性引发了事件。但是,我不喜欢测试 PropertyName 字符串的想法。它需要对属性名称进行硬编码,我已经在模型中使用 PropertyChanged.Notify(()=> PropertyName) 之类的调用避免了这种情况@

我看到的第二个选项是为我的所有属性实现单个事件:

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

....

最佳做法是什么?我更喜欢第二种选择。

编辑:我尝试更具体

我的模型类是这样工作的:

  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
           }
        }

这完全不安全,因为我的模型中的属性名称可以更改,我必须在不同的地方修复它。

【问题讨论】:

    标签: c# wpf properties event-handling inotifypropertychanged


    【解决方案1】:

    如果我正确理解您的问题,您可以使用以下内容:

    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。至少这是一个开始:)

    【讨论】:

    • 我已经在用这个了。问题更多的是如何区分一旦引发事件可能已经改变的属性。
    • 这正是RegisterPropertyHandler 方法正在做的事情,通过检查事件的PropertyName
    • 你是对的!对不起,我没有仔细研究你的例子。然而,它就像一个魅力。
    【解决方案2】:

    如果您的目标是 .NET 4.5,使用新的 CallerMemberName 属性实现 INotifyPropertyChanged 会更容易、更安全。

    简而言之,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.
    }
    

    您可以查看如何使用它的示例here

    至于选择哪个选项 - 我认为执行时间方面的差异可以忽略不计,而不是编码开销和代码混乱(无论是在代码本身还是在智能感知中)你都会因为有一个额外的事件而获得每个属性。我肯定会选择第一个选项。

    编辑:

    不幸的是,在处理PropertyChanged 事件时,您只能针对PropertyName 字符串进行测试,即使属性名称更改,也无法以保持一致的方式获取该字符串。对于依赖属性,您有 MyDependencyProperty.Name,但这不适用于常规属性。

    最终,您的选择是为每个属性使用不同的事件,或者在定义包含属性名称的属性的类中定义一个常量,希望您在/如果您更改属性时记得修改它姓名。假设您没有很多实现 INotifyPropertyChanged 的类,您自己在其中附加一个处理程序,那么为这些特定类中的每个属性设置一个事件并不是那么糟糕。

    【讨论】:

    • 我已经在做类似的事情了。问题是如何确定谁调用了该事件。请参阅我更新的问题。
    • 有可能...看看 khillang 的答案。
    【解决方案3】:

    只是对你的项目扩展方法是这样的:

        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=&gt;t.PropertyName))

    这并不理想,但没有字符串很难做到这一点。

    【讨论】:

    • 我已经这样做了:PropertyChanged.Notify(() => LinkDictionary);问题是如何在侦听器端执行此操作: void _MCDAExtensionPropertyChanged(object sender, PropertyChangedEventArgs e) { if(e.PropertyName.Equals("MyPropertyName")
    • LinkDictionary 到底是什么字符串常量?我的解决方案的整个想法是你没有到处都有字符串。您正在使用扩展方法从实例中获取字符串。在你的听众上,你必须有你的听众的实例。所以将它添加到_MCDAExtensionPropertyChanged(object sender, PropertyChangedEventArgs e) { if(e.PropertyName.Equals(listeningObj=&gt;listeningObj.GetPropertyName(l=&gt;l.MyPropertyName) 到您的属性MyPropertyName 在侦听器方面。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 2020-09-02
    • 2011-01-17
    • 1970-01-01
    • 2018-04-25
    • 1970-01-01
    相关资源
    最近更新 更多