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

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

 

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

 问题:

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

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

普通的实现方式

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 的时候就可以查出属性名是否对应,提高写代码的效率,减少出错的机会

你可能感兴趣的:(property)