DataBindings 与 INotifyPropertyChanged 实现自动刷新 WinForm 界面

--首发于博客园, 转载请保留此链接  博客原文地址

业务逻辑与界面的分离对于维护与迁移是非常重要的,在界面上给某属性赋值,后台要检测到其已经发生变化

 问题:

 输入某物品 单价 Price, 数量Amount, 要求自动计算总价,即: TotalPrice = Price * Amount, 如下图:

普通的实现方式

TextBox.TextChanged() 事件中可以检测到值发生改变,并且可以给其他的 TextBox.Text 赋值,但是如果 TextBox 太多,给每个 TextBox 加这样的一个事件工作量会比较大。

下面介绍另外一种方法:

使用 DataBindings 与 INotifyPropertyChanged 配合可以轻松实现这个需求。

Step 1. 先写一个类 NotifyPropertyChanged 继承 INotifyPropertyChanged 实现 OnPropertyChanged , 当界面添加 PropertyChanged 的事件时可以实现刷新

    public class NotifyPropertyChanged : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public bool SuppressNotifyPropertyChanged { get; set; }
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null && !SuppressNotifyPropertyChanged)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected virtual void OnPropertyChanged<T>(Expression<Func<T>> expr)
        {
            this.OnPropertyChanged(Utils.GetMemberName(expr));
        }

        /// <summary>
        /// 如果没有其他的业务逻辑,对 lambda 表达式比较熟悉的同学可以考虑用以下方法实现属性名称传递        
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propField"></param>
        /// <param name="value"></param>
        /// <param name="expr"></param>
        protected void SetProperty<T>(ref T propField, T value, Expression<Func<T>> expr)
        {
            var bodyExpr = expr.Body as System.Linq.Expressions.MemberExpression;
            if (bodyExpr == null)
            {
                throw new ArgumentException("Expression must be a MemberExpression!", "expr");
            }
            var propInfo = bodyExpr.Member as PropertyInfo;
            if (propInfo == null)
            {
                throw new ArgumentException("Expression must be a PropertyExpression!", "expr");
            }
            var propName = propInfo.Name;
            propField = value;
            this.OnPropertyChanged(propName);
        }

    }

    public class Utils
    {
        public static string GetMemberName<T>(Expression<Func<T>> expr)
        {
            var bodyExpr = expr.Body as System.Linq.Expressions.MemberExpression;
            if (bodyExpr == null)
                return string.Empty;
            return bodyExpr.Member.Name;
        }
    }

 Step 2. 写一个类 实现 Price, Amount, TotalPrice 的逻辑, 当然这个类继承 NotifyPropertyChanged 

    public class TestBindingClass : NotifyPropertyChanged
    {
        #region Properties

        private decimal _price;

        public decimal Price
        {
            get { return _price; }
            set
            {
                _price = value;
                _totalPrice = Amount * Price;   
                OnPropertyChanged(() => this.Price);
                OnPropertyChanged(() => this.TotalPrice);
            }
        }

        private decimal _amount;

        public decimal Amount
        {
            get { return _amount; }
            set
            {
                _amount = value;
                _totalPrice = Amount * Price;
                OnPropertyChanged(() => this.Amount);
                OnPropertyChanged(() => this.TotalPrice);
            }
        }

        private decimal _totalPrice;

        public decimal TotalPrice
        {
            get { return _totalPrice; }
            set
            {
                _totalPrice = value;
                if (Amount != 0)
                    _price = TotalPrice / Amount; // Note:don't call method Price_Set, or it goes into an infinite loop
                OnPropertyChanged(() => this.TotalPrice);
                OnPropertyChanged(() => this.Price);
            }
        }

        #endregion
    }

 Step 3. 界面绑定相关属性,这样就给相关的属性加了 PropertyChanged 事件

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            textBox1.DataBindings.Add(ControlBindingProperty.Text, myClass, () => myClass.Price);
            textBox2.DataBindings.Add(ControlBindingProperty.Text, myClass, () => myClass.Amount);
            textBox3.DataBindings.Add(ControlBindingProperty.Text, myClass, () => myClass.TotalPrice);
        }

        TestBindingClass myClass = new TestBindingClass();
    }
    
    public static class GUIUtils
    {
        public static void Add<T>(this ControlBindingsCollection bindings, string propertyName, object dataSource, Expression<Func<T>> expr)
        {
            string dataMember = Utils.GetMemberName(expr);
            bindings.Add(propertyName, dataSource, dataMember);
        }
    }

    public static class ControlBindingProperty
    {
        public const string Text = "Text";
    }

总结: 

(1). 在其他属性的 set 方法里给其他属性赋值时最好用小写的属性名,而不要直接调用 OtherProperty_set 方法, 否则容易进入死循环

(2). 可以看到step 3 里,添加了 ControlBindingsCollection 的扩展方法,这样就不用担心"PropertyName" 之类容易出错的看起来很恶心的写法,事实上 IDE 的提示功能使 lambda 表达式写起来非常方便,并且在 Build 的时候就可以查出属性名是否对应,提高写代码的效率,减少出错的机会

原文地址:https://www.cnblogs.com/EasyInvoice/p/3832092.html