Better PropertyChanged and PropertyChanging event handling

前端 未结 3 1589
故里飘歌
故里飘歌 2020-12-14 21:11

I am implementing the observer pattern for our application - currently playing around with the RX Framework.

I currently have an example that looks like this:

<
3条回答
  •  情歌与酒
    2020-12-14 21:47

    For anyone that does want the best of both RX and being able to Cancel here is a hybrid of both of these ideas

    The ViewModel base class stuff

    public abstract class INPCBase : INotifyPropertyChanged, INotifyPropertyChanging
    {
        public event PropertyChangingEventHandler PropertyChanging;
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected bool OnPropertyChanging(string propertyName, T originalValue, T newValue)
        {
            var handler = this.PropertyChanging;
            if (handler != null)
            {
                var args = new PropertyChangingCancelEventArgs(propertyName, originalValue, newValue);
                handler(this, args);
                return !args.Cancel;
            }
            return true;
        }
    
        protected void OnPropertyChanged(string propertyName, T previousValue, T currentValue)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName, previousValue, currentValue));
        }
    }
    
    
    public class PropertyChangingCancelEventArgs : PropertyChangingEventArgs
    {
        public bool Cancel { get; set; }
    
        public PropertyChangingCancelEventArgs(string propertyName)
            : base(propertyName)
        {
        }
    }
    
    public class PropertyChangingCancelEventArgs : 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 : 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;
        }
    }
    

    Then I have these couple extensions.

    One to get the property name from Expression tree

    public static class ExpressionExtensions
    {
    
        public static string GetPropertyName(this Expression> 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(this Expression> 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(this Expression> action)
        {
            MemberExpression ex = (MemberExpression)action.Body;
            return ex.Member.Name;
        }
    
        public static void CheckIsNotNull(this Expression> action, string message)
        {
            MemberExpression ex = (MemberExpression)action.Body;
            string memberName = ex.Member.Name;
            if (action.Compile()() == null)
            {
                throw new ArgumentNullException(memberName, message);
            }
        }
    
    }
    

    And then the Rx part

    public static class ObservableExtensions
    {
    
        public static IObservable> ObserveSpecificPropertyChanging(
            this TItem target, Expression> propertyName) where TItem : INotifyPropertyChanging
        {
            var property = propertyName.GetPropertyName();
    
            return ObserveSpecificPropertyChanging(target, property)
                   .Select(i => new ItemPropertyChangingEvent()
                   {
                       OriginalEventArgs = (PropertyChangingCancelEventArgs)i.OriginalEventArgs,
                       Property = i.Property,
                       Sender = i.Sender
                   });
        }
    
        public static IObservable> ObserveSpecificPropertyChanging(
            this TItem target, string propertyName = null) where TItem : INotifyPropertyChanging
        {
    
            return Observable.Create>(obs =>
            {
                Dictionary properties = new Dictionary();
                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()
                        {
                            Sender = target,
                            Property = prop,
                            OriginalEventArgs = a,
                        };
    
                        obs.OnNext(change);
                    }
                };
    
                target.PropertyChanging += handler;
    
                return () =>
                {
                    target.PropertyChanging -= handler;
                };
            });
        }
    
    
    
        public class ItemPropertyChangingEvent
        {
            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
        {
            public TSender Sender { get; set; }
            public PropertyInfo Property { get; set; }
            public PropertyChangingCancelEventArgs OriginalEventArgs { get; set; }
        }
    
    }
    

    Then example usage will be like this

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

    Works a treat

提交回复
热议问题