WPF EditableObject

Introduction

EditableObject is designed as an advanced view model property editor. It encapsulates dirty/edited/originalValue etc.. functions within properties of Model(TObj). So that binding EditableProperty in UI can easily notify when value is changed, or rollback to original value, or save value.

This object use Linq Expression and Reflection to get or set values of an object.
While I'm writing this, my c# version has no support for nameof keyword, so I have to use Linq Expression instead, or there will be a more flexible and clear codes.

Usage ViewModel

    public partial class EditableObjectTest : ViewModelBase
    {
        public EditableObject EditablePerson { get; }
        public EditableProperty EditableFirstName => EditablePerson[() => Model.FirstName];
        public EditableProperty EditableLastName => EditablePerson[() => Model.LastName];
        public EditableProperty EditableAddress => EditablePerson[() => Model.Address];
        public Person Model { get; set; }
        public EditableObjectTest()
        {
            Model = new Person {FirstName = "Z", LastName = "w", Address = "1003No"};

            EditablePerson = new EditableObject(Model,
                new Expression>[] //System.Linq.Expressions.Expression
                {
                    () => Model.FirstName,
                    () => Model.LastName,
                    () => Model.Address,
                } );
            EditablePerson.PropertyChanged += property => { };
        }
    }

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
    }

Usage of UI

        
            
                
            
        

Object Structure

    public class EditableObject
    {
        private bool _canMarkDirty;

        public event Action> PropertyChanged;

        public TObj SourceObject { get; }
        public Dictionary> PropertyDictionary { get; }

        /// 
        /// A flag that determines whether each editable property can be marked as modified or not if value is changed.
        /// 
        public bool CanMarkDirty
        {
            get { return _canMarkDirty; }
            set
            {
                _canMarkDirty = value;
                foreach (var prop in PropertyDictionary.Values)
                {
                    prop.CanMarkDirty = value;
                }
            }
        }

        public EditableObject(TObj sourceObj, IEnumerable>> propExpressions)
        {
            SourceObject = sourceObj;
            PropertyDictionary = new Dictionary>();
            foreach (var propExp in propExpressions)
            {
                var propInfo = ExtractExpressionToPropInfo(propExp);
                if (propInfo == null)
                {
                    continue;
                }

                var key = propInfo.Name;
                var getValueFunc = propExp.Compile();
                var value = getValueFunc();

                var newProp = new EditableProperty(value);
                newProp.ValueChanged += sender =>
                {
                    SetEditedValue(sender, sourceObj);
                    PropertyChanged?.Invoke(sender);
                };

                PropertyDictionary.Add(key, newProp);
            }
        }

        /// 
        /// Checks if there're properties that are modified.
        /// 
        public bool ContainsModifications()
        {
            return PropertyDictionary.Values.Any(o => o.IsDirty);
        }

        /// 
        /// Sets modified property value to to specific object.
        /// 
        public void SetEditedValue(EditableProperty editableProp, TObj targetObj)
        {
            var key = PropertyDictionary.First(p => p.Value == editableProp).Key;
            typeof(TObj).GetProperty(key).SetValue(targetObj, editableProp.Value);
        }

        /// 
        /// Sets all modified property values to to specific object.
        /// 
        public void SetEditedValuesTo(TObj targetObj)
        {
            foreach (var prop in PropertyDictionary.Values)
            {
                SetEditedValue(prop, targetObj);
            }
        }

        /// 
        /// Saves all s' values (including setting original value and reset modified flag).
        /// 
        public void SaveChanges()
        {
            foreach (var prop in PropertyDictionary.Values)
            {
                prop.SaveValue();
            }
        }

        /// 
        /// Discards all s' values (including setting original value and reset modified flag).
        /// 
        public void DiscardChanges()
        {
            foreach (var prop in PropertyDictionary.Values)
            {
                prop.ResetValue();
            }
        }

        /// 
        /// Extracts  from property expression.
        /// 
        private PropertyInfo ExtractExpressionToPropInfo(Expression> propExpression)
        {
            var propInfo = (propExpression.Body as MemberExpression)?.Member as PropertyInfo;
            if (propInfo == null)
            {
                Console.WriteLine("EditableObjectCollection ExtractExpressionToPropInfo failed: Cannot resolve expression: {0}", propExpression.Name);
            }
            return propInfo;
        }

        /// 
        /// The Indexer that gets  via specific property expression.
        /// 
        public EditableProperty this[Expression> propExpression] => PropertyDictionary[ExtractExpressionToPropInfo(propExpression).Name];
    }

    public class EditableProperty : ObservableObject
    {
        private T _value;
        private T _originalValue;
        private bool _isDirty;
        private bool _canMarkDirty;

        public event Action> ValueChanged;

        /// 
        /// Please use SetValue method instead of Property-Setter.
        /// 

        public T Value
        {
            get { return _value; }
            set
            {
                _value = value;
                RaisePropertyChanged(() => Value);
                if (CanMarkDirty)
                {
                    //special case: string.Empty and null is regarded as same.
                    if (typeof(T) == typeof(string) &&
                        string.IsNullOrEmpty(Value as string) &&
                        string.IsNullOrEmpty(OriginalValue as string))
                    {
                        IsDirty = false;
                    }
                    else
                    {
                        IsDirty = !Equals(Value, OriginalValue);
                    }
                }
                ValueChanged?.Invoke(this);
            }
        }


        /// 
        /// The original value for value modification comparison.
        /// 
        public T OriginalValue
        {
            get { return _originalValue; }
            set
            {
                _originalValue = value;
                RaisePropertyChanged(() => OriginalValue);
            }
        }

        /// 
        /// Determines if value is modified.
        /// 
        public bool IsDirty
        {
            get { return _isDirty; }
            set
            {
                _isDirty = value;
                RaisePropertyChanged(() => IsDirty);
            }
        }

        /// 
        /// A flag that determines whether this property can be marked as modified or not if value is changed.
        /// 
        public bool CanMarkDirty
        {
            get { return _canMarkDirty; }
            set
            {
                _canMarkDirty = value;
                RaisePropertyChanged(() => CanMarkDirty);
            }
        }

        public EditableProperty(T value)
        {
            Value = value;
            OriginalValue = value;
            IsDirty = false;
            CanMarkDirty = true;
        }

        /// 
        /// Saves the value modification so that original value is consistent with value and modified flag is false.
        /// 
        public void SaveValue()
        {
            OriginalValue = Value;
            IsDirty = false;
        }

        /// 
        /// Discards the value modification so that original value is consistent with value and modified flag is false.
        /// 
        public void ResetValue()
        {
            Value = OriginalValue;
            IsDirty = false;
        }
    }

你可能感兴趣的:(WPF EditableObject)