美文网首页C# & WPF
WPF EditableObject

WPF EditableObject

作者: Kreiven | 来源:发表于2018-06-06 11:30 被阅读0次

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<Person, string> EditablePerson { get; }
        public EditableProperty<string> EditableFirstName => EditablePerson[() => Model.FirstName];
        public EditableProperty<string> EditableLastName => EditablePerson[() => Model.LastName];
        public EditableProperty<string> EditableAddress => EditablePerson[() => Model.Address];
        public Person Model { get; set; }
        public EditableObjectTest()
        {
            Model = new Person {FirstName = "Z", LastName = "w", Address = "1003No"};

            EditablePerson = new EditableObject<Person, string>(Model,
                new Expression<Func<string>>[] //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

        <TextBox  Text="{Binding EditableFirstName.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
            <TextBox.Style>
                <Style>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding EditableFirstName.IsDirty}" Value="True">
                            <Setter Property="TextBox.Background" Value="Yellow"></Setter>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
        </TextBox>

Object Structure

    public class EditableObject<TObj, TValue>
    {
        private bool _canMarkDirty;

        public event Action<EditableProperty<TValue>> PropertyChanged;

        public TObj SourceObject { get; }
        public Dictionary<string, EditableProperty<TValue>> PropertyDictionary { get; }

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

        public EditableObject(TObj sourceObj, IEnumerable<Expression<Func<TValue>>> propExpressions)
        {
            SourceObject = sourceObj;
            PropertyDictionary = new Dictionary<string, EditableProperty<TValue>>();
            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<TValue>(value);
                newProp.ValueChanged += sender =>
                {
                    SetEditedValue(sender, sourceObj);
                    PropertyChanged?.Invoke(sender);
                };

                PropertyDictionary.Add(key, newProp);
            }
        }

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

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

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

        /// <summary>
        /// Saves all <see cref="EditableProperty{T}"/>s' values (including setting original value and reset modified flag).
        /// </summary>
        public void SaveChanges()
        {
            foreach (var prop in PropertyDictionary.Values)
            {
                prop.SaveValue();
            }
        }

        /// <summary>
        /// Discards all <see cref="EditableProperty{T}"/>s' values (including setting original value and reset modified flag).
        /// </summary>
        public void DiscardChanges()
        {
            foreach (var prop in PropertyDictionary.Values)
            {
                prop.ResetValue();
            }
        }

        /// <summary>
        /// Extracts <see cref="PropertyInfo"/> from property expression.
        /// </summary>
        private PropertyInfo ExtractExpressionToPropInfo(Expression<Func<TValue>> 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;
        }

        /// <summary>
        /// The Indexer that gets <see cref="EditableProperty{T}"/> via specific property expression.
        /// </summary>
        public EditableProperty<TValue> this[Expression<Func<TValue>> propExpression] => PropertyDictionary[ExtractExpressionToPropInfo(propExpression).Name];
    }

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

        public event Action<EditableProperty<T>> ValueChanged;

        /// <summary>
        /// Please use SetValue method instead of Property-Setter.
        /// </summary>

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


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

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

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

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

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

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

相关文章

  • WPF EditableObject

    Introduction EditableObject is designed as an advanced vi...

  • WPF简介

    目录 什么是WPF? WPF的历史? 为什么要用WPF及WPF作用 WPF与winForm区别? 什么是WPF? ...

  • 【WPF】WPF介绍

    一、WPF简介 WPF:WPF即Windows Presentation Foundation,翻译为中文“Win...

  • WPF/C#学习笔记.1:WPF中的布局TabControl,G

    WPF/C#学习笔记.1 WPF中的布局TabControl,Grid与GridSpliter等 WPF布局原则 ...

  • 1. WPF概述

    wpf是什么 wpf(windows presentation foundation)是用于windows的现代图...

  • WPF与WinForm开发有什么区别?

    WPF开发于WinForm之后,从技术发展的角度,WPF比WinForm先进是不容置疑的。我觉得WPF相比于Win...

  • wpf 中的无效绑定

    设置wpf绑定的跟踪级别为high,output中观察wpf的处理流程: 对于一个无效的绑定,wpf尝试了5次,最...

  • WPF入门

    ``` wpf入门 ```

  • Xaml GUI开发的当下.md

    XAML的诞生 Xaml是微软发布WPF时提出的GUI布局描述技术。 为何不用WPF 微软一贯的尿性,WPF已经不...

  • WPF初印象

    一、与WPF结缘 第一次接触WPF是在2012年。学习了WinForm编程之后,了解到WPF这个非常先进的UI框架...

网友评论

    本文标题:WPF EditableObject

    本文链接:https://www.haomeiwen.com/subject/hoqbsftx.html