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

  • 相关阅读:
    Oracle中的4大空值处理函数用法举例
    PyCharm安装
    Python安装与环境变量的配置
    多层分组排序问题
    将时间点的数据变成时间段的数据
    根据状态变化情况,求最大值和最小值
    ubuntu 源码安装 swig
    CSDN博客排名第一名,何许人也
    thinkPHP的常用配置项
    拔一拔 ExtJS 3.4 里你遇到的没遇到的 BUG(1)
  • 原文地址:https://www.cnblogs.com/EasyInvoice/p/3832092.html
Copyright © 2020-2023  润新知