【问题标题】:Better PropertyChanged and PropertyChanging event handling更好的 PropertyChanged 和 PropertyChanging 事件处理
【发布时间】:2011-12-20 14:47:22
【问题描述】:

我正在为我们的应用程序实现观察者模式 - 目前正在使用 RX 框架。

我目前有一个如下示例:

Observable.FromEventPattern<PropertyChangedEventArgs>(Instance.Address, "PropertyChanged")
    .Where(e => e.EventArgs.PropertyName == "City")
    .ObserveOn(Scheduler.ThreadPool)
    .Subscribe(search => OnNewSearch(search.EventArgs));

(我有一个类似的“PropertyChanging”)

EventArgs 并没有给我太多。我想要的是 EventArgs 的扩展,它使我能够查看以前的值和新值,以及在“更改”侦听器中标记事件的能力,这样更改实际上不会持续存在。如何才能做到这一点?谢谢。

【问题讨论】:

标签: c# .net c#-4.0


【解决方案1】:

我认为这取决于您如何实现 INotifyPropertyChanging 和 INotifyPropertyChanged 接口。

很遗憾,PropertyChangingEventArgs 和 PropertyChangedEventArgs 类不提供属性的前后值或取消更改的能力,但您可以派生提供该功能的自己的事件参数类。

首先,定义以下事件参数类。请注意,这些派生自 PropertyChangingEventArgs 类和 PropertyChangedEventArgs 类。这允许我们将这些对象作为参数传递给 PropertyChangingEventHandler 和 PropertyChangedEventHandler 委托。

class PropertyChangingCancelEventArgs : PropertyChangingEventArgs
{
    public bool Cancel { get; set; }

    public PropertyChangingCancelEventArgs(string propertyName)
        : base(propertyName)
    {
    }
}

class PropertyChangingCancelEventArgs<T> : PropertyChangingCancelEventArgs
{
    public T OriginalValue { get; private set; }

    public T NewValue { get; private set; }

    public PropertyChangingCancelEventArgs(string propertyName, T originalValue, T newValue)
        : base(propertyName)
    {
        this.OriginalValue = originalValue;
        this.NewValue = newValue;
    }
}

class PropertyChangedEventArgs<T> : PropertyChangedEventArgs
{
    public T PreviousValue { get; private set; }

    public T CurrentValue { get; private set; }

    public PropertyChangedEventArgs(string propertyName, T previousValue, T currentValue)
        : base(propertyName)
    {
        this.PreviousValue = previousValue;
        this.CurrentValue = currentValue;
    }
}

接下来,您需要在 INotifyPropertyChanging 和 INotifyPropertyChanged 接口的实现中使用这些类。一个实现的例子如下:

class Example : INotifyPropertyChanging, INotifyPropertyChanged
{
    public event PropertyChangingEventHandler PropertyChanging;

    public event PropertyChangedEventHandler PropertyChanged;

    protected bool OnPropertyChanging<T>(string propertyName, T originalValue, T newValue)
    {
        var handler = this.PropertyChanging;
        if (handler != null)
        {
            var args = new PropertyChangingCancelEventArgs<T>(propertyName, originalValue, newValue);
            handler(this, args);
            return !args.Cancel;
        }
        return true;
    }

    protected void OnPropertyChanged<T>(string propertyName, T previousValue, T currentValue)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs<T>(propertyName, previousValue, currentValue));
    }

    int _ExampleValue;

    public int ExampleValue
    {
        get { return _ExampleValue; }
        set
        {
            if (_ExampleValue != value)
            {
                if (this.OnPropertyChanging("ExampleValue", _ExampleValue, value))
                {
                    var previousValue = _ExampleValue;
                    _ExampleValue = value;
                    this.OnPropertyChanged("ExampleValue", previousValue, value);
                }
            }
        }
    }
}

请注意,PropertyChanging 和 PropertyChanged 事件的事件处理程序仍需要将原始 PropertyChangingEventArgs 类和 PropertyChangedEventArgs 类作为参数,而不是更具体的版本。但是,您可以将事件 args 对象转换为更具体的类型,以便访问新属性。

以下是这些事件的事件处理程序示例:

class Program
{
    static void Main(string[] args)
    {
        var exampleObject = new Example();

        exampleObject.PropertyChanging += new PropertyChangingEventHandler(exampleObject_PropertyChanging);
        exampleObject.PropertyChanged += new PropertyChangedEventHandler(exampleObject_PropertyChanged);

        exampleObject.ExampleValue = 123;
        exampleObject.ExampleValue = 100;
    }

    static void exampleObject_PropertyChanging(object sender, PropertyChangingEventArgs e)
    {
        if (e.PropertyName == "ExampleValue")
        {
            int originalValue = ((PropertyChangingCancelEventArgs<int>)e).OriginalValue;
            int newValue = ((PropertyChangingCancelEventArgs<int>)e).NewValue;

            // do not allow the property to be changed if the new value is less than the original value
            if(newValue < originalValue)
                ((PropertyChangingCancelEventArgs)e).Cancel = true;
        }

    }

    static void exampleObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "ExampleValue")
        {
            int previousValue = ((PropertyChangedEventArgs<int>)e).PreviousValue;
            int currentValue = ((PropertyChangedEventArgs<int>)e).CurrentValue;
        }
    }
}

【讨论】:

  • 是否可以使用 DynamicProxy 来实现这一点,以避免 setter 方法中的所有代码?
  • @user981225 - 我不熟悉 DynamicProxy(例如 Castle?),但我猜您的意思是采用 AOP 或策略注入方法。我同意您的想法,我肯定会寻找某种机制(AOP、策略注入或其他)来整合该属性设置器代码,这样您就不会有很多冗余代码。不过,我没有任何特别的机制可以推荐。
  • 非常感谢您的帮助,非常感谢。如果我能找到一个优雅的代理解决方案,我会在这里发布。
  • 这是有用的信息,但我……像往常一样……被事件的订阅者必须将属性特定的 Changed 和 Changed 事件转换为自定义参数类型的事实所困扰。访问内部字段。我认为应该有一种不需要演员表的方法来做到这一点,但我找不到。
  • @Dr.Wily'sApprentice 我怎样才能访问Example 上的.PropertyChanging += 成员?这是哪里来的?
【解决方案2】:

接受的响应非常糟糕,您可以简单地使用 Buffer() 来做到这一点。

Observable.FromEventPattern<PropertyChangedEventArgs>(Instance.Address, "PropertyChanged")
    .Where(e => e.EventArgs.PropertyName == "City")
    .Buffer(2,1)  //Take 2 events at a time, every 1 event
    .ObserveOn(Scheduler.ThreadPool)
    .Subscribe(search => ...); //search[0] is old value, search[1] is new value

【讨论】:

  • 嗯...几个问题:1) 内置的 PropertyChanged/PropertyChanging 事件不提供属性的新值或原始值,这对于这种“缓冲区”方法来说是必要的工作。 2) 这种方法要求事件多次触发,以便您可以比较两个事件之间更改的属性值。 OP 可能希望能够仅观察单个事件的属性的先前值和当前值。 3) OP 希望能够取消对属性值的更改。内置的 PropertyChanging 事件不提供取消机制。
【解决方案3】:

对于任何想要充分利用 RX 并能够在此处取消的人来说,这是这两种想法的混合体

ViewModel 基类的东西

public abstract class INPCBase : INotifyPropertyChanged, INotifyPropertyChanging
{
    public event PropertyChangingEventHandler PropertyChanging;

    public event PropertyChangedEventHandler PropertyChanged;

    protected bool OnPropertyChanging<T>(string propertyName, T originalValue, T newValue)
    {
        var handler = this.PropertyChanging;
        if (handler != null)
        {
            var args = new PropertyChangingCancelEventArgs<T>(propertyName, originalValue, newValue);
            handler(this, args);
            return !args.Cancel;
        }
        return true;
    }

    protected void OnPropertyChanged<T>(string propertyName, T previousValue, T currentValue)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs<T>(propertyName, previousValue, currentValue));
    }
}


public class PropertyChangingCancelEventArgs : PropertyChangingEventArgs
{
    public bool Cancel { get; set; }

    public PropertyChangingCancelEventArgs(string propertyName)
        : base(propertyName)
    {
    }
}

public class PropertyChangingCancelEventArgs<T> : PropertyChangingCancelEventArgs
{
    public T OriginalValue { get; private set; }

    public T NewValue { get; private set; }

    public PropertyChangingCancelEventArgs(string propertyName, T originalValue, T newValue)
        : base(propertyName)
    {
        this.OriginalValue = originalValue;
        this.NewValue = newValue;
    }
}

public class PropertyChangedEventArgs<T> : PropertyChangedEventArgs
{
    public T PreviousValue { get; private set; }

    public T CurrentValue { get; private set; }

    public PropertyChangedEventArgs(string propertyName, T previousValue, T currentValue)
        : base(propertyName)
    {
        this.PreviousValue = previousValue;
        this.CurrentValue = currentValue;
    }
}

然后我有这几个扩展。

从表达式树中获取属性名称

public static class ExpressionExtensions
{

    public static string GetPropertyName<TProperty>(this Expression<Func<TProperty>> expression)
    {
        var memberExpression = expression.Body as MemberExpression;
        if (memberExpression == null)
        {
            var unaryExpression = expression.Body as UnaryExpression;
            if (unaryExpression != null)
            {
                if (unaryExpression.NodeType == ExpressionType.ArrayLength)
                    return "Length";
                memberExpression = unaryExpression.Operand as MemberExpression;

                if (memberExpression == null)
                {
                    var methodCallExpression = unaryExpression.Operand as MethodCallExpression;
                    if (methodCallExpression == null)
                        throw new NotImplementedException();

                    var arg = (ConstantExpression)methodCallExpression.Arguments[2];
                    return ((MethodInfo)arg.Value).Name;
                }
            }
            else
                throw new NotImplementedException();

        }

        var propertyName = memberExpression.Member.Name;
        return propertyName;

    }

    public static string GetPropertyName<T, TProperty>(this Expression<Func<T, TProperty>> expression)
    {
        var memberExpression = expression.Body as MemberExpression;

        if (memberExpression == null)
        {
            var unaryExpression = expression.Body as UnaryExpression;

            if (unaryExpression != null)
            {
                if (unaryExpression.NodeType == ExpressionType.ArrayLength)
                    return "Length";
                memberExpression = unaryExpression.Operand as MemberExpression;

                if (memberExpression == null)
                {
                    var methodCallExpression = unaryExpression.Operand as MethodCallExpression;
                    if (methodCallExpression == null)
                        throw new NotImplementedException();

                    var arg = (ConstantExpression)methodCallExpression.Arguments[2];
                    return ((MethodInfo)arg.Value).Name;
                }
            }
            else
                throw new NotImplementedException();
        }
        var propertyName = memberExpression.Member.Name;
        return propertyName;

    }

    public static String PropertyToString<R>(this Expression<Func<R>> action)
    {
        MemberExpression ex = (MemberExpression)action.Body;
        return ex.Member.Name;
    }

    public static void CheckIsNotNull<R>(this Expression<Func<R>> action, string message)
    {
        MemberExpression ex = (MemberExpression)action.Body;
        string memberName = ex.Member.Name;
        if (action.Compile()() == null)
        {
            throw new ArgumentNullException(memberName, message);
        }
    }

}

然后是 Rx 部分

public static class ObservableExtensions
{

    public static IObservable<ItemPropertyChangingEvent<TItem, TProperty>> ObserveSpecificPropertyChanging<TItem, TProperty>(
        this TItem target, Expression<Func<TItem, TProperty>> propertyName) where TItem : INotifyPropertyChanging
    {
        var property = propertyName.GetPropertyName();

        return ObserveSpecificPropertyChanging(target, property)
               .Select(i => new ItemPropertyChangingEvent<TItem, TProperty>()
               {
                   OriginalEventArgs = (PropertyChangingCancelEventArgs<TProperty>)i.OriginalEventArgs,
                   Property = i.Property,
                   Sender = i.Sender
               });
    }

    public static IObservable<ItemPropertyChangingEvent<TItem>> ObserveSpecificPropertyChanging<TItem>(
        this TItem target, string propertyName = null) where TItem : INotifyPropertyChanging
    {

        return Observable.Create<ItemPropertyChangingEvent<TItem>>(obs =>
        {
            Dictionary<string, PropertyInfo> properties = new Dictionary<string, PropertyInfo>();
            PropertyChangingEventHandler handler = null;

            handler = (s, a) =>
            {
                if (propertyName == null || propertyName == a.PropertyName)
                {
                    PropertyInfo prop;
                    if (!properties.TryGetValue(a.PropertyName, out prop))
                    {
                        prop = target.GetType().GetProperty(a.PropertyName);
                        properties.Add(a.PropertyName, prop);
                    }
                    var change = new ItemPropertyChangingEvent<TItem>()
                    {
                        Sender = target,
                        Property = prop,
                        OriginalEventArgs = a,
                    };

                    obs.OnNext(change);
                }
            };

            target.PropertyChanging += handler;

            return () =>
            {
                target.PropertyChanging -= handler;
            };
        });
    }



    public class ItemPropertyChangingEvent<TSender>
    {
        public TSender Sender { get; set; }
        public PropertyInfo Property { get; set; }
        public PropertyChangingEventArgs OriginalEventArgs { get; set; }

        public override string ToString()
        {
            return string.Format("Sender: {0}, Property: {1}", Sender, Property);
        }
    }


    public class ItemPropertyChangingEvent<TSender, TProperty>
    {
        public TSender Sender { get; set; }
        public PropertyInfo Property { get; set; }
        public PropertyChangingCancelEventArgs<TProperty> OriginalEventArgs { get; set; }
    }

}

那么示例用法会是这样的

public class MainWindowViewModel : INPCBase
{
    private string field1;
    private string field2;


    public MainWindowViewModel()
    {
        field1 = "Hello";
        field2 = "World";

        this.ObserveSpecificPropertyChanging(x => x.Field2)
           .Subscribe(x =>
           {
               if (x.OriginalEventArgs.NewValue == "DOG")
               {
                   x.OriginalEventArgs.Cancel = true;
               }
           });

    }

    public string Field1
    {
        get
        {
            return field1;
        }
        set
        {
            if (field1 != value)
            {
                if (this.OnPropertyChanging("Field1", field1, value))
                {
                    var previousValue = field1;
                    field1 = value;
                    this.OnPropertyChanged("Field1", previousValue, value);
                }
            }
        }
    }


    public string Field2
    {
        get
        {
            return field2;
        }
        set
        {
            if (field2 != value)
            {
                if (this.OnPropertyChanging("Field2", field2, value))
                {
                    var previousValue = field2;
                    field2 = value;
                    this.OnPropertyChanged("Field2", previousValue, value);
                }
            }
        }
    }
}

工作愉快

【讨论】:

    猜你喜欢
    • 2010-10-11
    • 1970-01-01
    • 2014-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多