zoukankan      html  css  js  c++  java
  • WPF MVVM之INotifyPropertyChanged接口的几种实现方式(转)

    原地址:https://www.cnblogs.com/xiwang/archive/2012/11/25/2787358.html

    序言

           借助WPF/Sliverlight强大的数据绑定功能,可以比实现比MFC,WinForm更加优雅轻松的数据绑定。但是在使用WPF/Silverlight绑定时,有件事情是很苦恼的:当ViewModel对象放生改变,需要通知UI。我们可以让VM对象实现INotifyPropertyChanged接口,通过事件来通知UI。但问题就出现这里……

    一,描述问题

            情形:现在需要将一个Person对象的Name熟悉双向绑定到UI中的TextBox,的确这是一件很简单的事情,但还是描述下:

            XAML:

    <TextBox Text="{Binding Name,Mode=TwoWay}"/>

           C# Code:

    复制代码
    public class Person : INotifyPropertyChanged
        {
            private string m_Name;
            public string Name
            {
                get { return m_Name; }
                set 
                {
                    if (m_Name == value) return; 
                    m_Name = value;
                    this.Notify("Name");
                }
            }
    
            public Person()
            {
                this.m_Name = "墨梅,在这里......";
            }
    
    
            public event PropertyChangedEventHandler PropertyChanged;
            public void Notify(string propertyName)
            {
                PropertyChangedEventHandler handler = this.PropertyChanged;
                if (handler != null)
                    handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    复制代码

           是的,这就可以实现了。但是这里一个问题困惑我,曾经就在this.Notify("Name"),将参数写错,UI迟迟得不到响应。这个错误很难发现!!!也很难跟踪,但是这个细微的错误可以导致一个很严重的运行时错误。这的确是一件很苦恼的事情。

    二解决问题

    方法一:添加验证

    复制代码
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged(string propertyName)
    {
        this.VerifyPropertyName(propertyName);
    
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
    
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public void VerifyPropertyName(string propertyName)
    {
        // Verify that the property name matches a real,  
        // public, instance property on this object.
        if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        {
            string msg = "Invalid property name: " + propertyName;
    
            if (this.ThrowOnInvalidPropertyName)
                throw new Exception(msg);
            else
                Debug.Fail(msg);
        }
    }
    复制代码

         这里对验证事件参数使用条件编译[Conditional(“DEBUG”)],在release版本中这个函数是不会调用的,比使用#if 等有更明显有优势。

         这个方法虽然可以达到目的,但是还是那么的别扭,必须到运行时才能知道是否有错误,所以还是不怎么好。

    方法二,使用Lambda表达式,静态扩展语法

    复制代码
    public static class NotificationExtensions
        {
            public static void Notify(this PropertyChangedEventHandler eventHandler, Expression<Func<object>> expression)
            {
                if( null == eventHandler )
                {
                    return;
                }
                var lambda = expression as LambdaExpression;
                MemberExpression memberExpression;
                if (lambda.Body is UnaryExpression)
                {
                    var unaryExpression = lambda.Body as UnaryExpression;
                    memberExpression = unaryExpression.Operand as MemberExpression;
                }
                else
                {
                    memberExpression = lambda.Body as MemberExpression;
                }
                var constantExpression = memberExpression.Expression as ConstantExpression;
                var propertyInfo = memberExpression.Member as PropertyInfo;
                
                foreach (var del in eventHandler.GetInvocationList())
                {
                    del.DynamicInvoke(new object[] {constantExpression.Value, new PropertyChangedEventArgs(propertyInfo.Name)});
                }
            }
       }
    复制代码

    这里用使用的静态扩展语法,我还是比较喜欢这个的,但是并不是所有人都喜欢哦。如何使用呢:

    复制代码
    public class Employee : INotifyPropertyChanged
    
    {
    
        public event PropertyChangedEventHandler PropertyChanged;
    
    
    
        private string _firstName;
    
        public string FirstName 
    
        {
    
           get { return this._firstName; }
    
           set
    
           {
    
              this._firstName = value;
    
              this.PropertyChanged.Notify(()=>this.FirstName);
    
           }
    
        }
    
    }
    复制代码

    这里还可以添加一个很实用的扩展:

    复制代码
    public static void SubscribeToChange<T>(this T objectThatNotifies, Expression<Func<object>> expression, PropertyChangedEventHandler<T> handler)
                where T : INotifyPropertyChanged
            {
                objectThatNotifies.PropertyChanged +=
                    (s, e) =>
                        {
                            var lambda = expression as LambdaExpression;
                            MemberExpression memberExpression;
                            if (lambda.Body is UnaryExpression)
                            {
                                var unaryExpression = lambda.Body as UnaryExpression;
                                memberExpression = unaryExpression.Operand as MemberExpression;
                            }
                            else
                            {
                                memberExpression = lambda.Body as MemberExpression;
                            }
                            var propertyInfo = memberExpression.Member as PropertyInfo;
    
                            if(e.PropertyName.Equals(propertyInfo.Name))
                            {
                                handler(objectThatNotifies);
                            }
                        };
            }
    复制代码

    通过上面的代码,可以订阅熟悉改变事件,如:

    复制代码
    myObject.SubscripeToChange(()=>myObject.SomeProperty,SomeProperty_Changed); 
     And then your handler would look like this:
    
    private void SomeProperty_Changed(MyObject myObject)
    {
        /* ... implement something here */
    }
    复制代码

    方法三,net4.5,框架提供的解决方法

    复制代码
    private string m_myProperty;
    public string MyProperty
    {
        get { return m_myProperty; }
        set
        {
            m_myProperty = value;
            OnPropertyChanged();
        }
    }
    
    private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
    {
        // ... do stuff here ...
    }
    复制代码

    属性CallerMemberName的解决办法和方法二是基本相同的,不同的是这个在net框架中解决的。更多信息可以查看CallerMemberName,net4.5还提供了

    CallerFilePath,CallerLineNumber,这几很有用的语法

    方法四,这个也不错哦

    复制代码
    public static class SymbolExtensions
        {
            public static string GetPropertySymbol<T,R>(this T obj, Expression<Func<T,R>> expr)
            {
                return ((MemberExpression)expr.Body).Member.Name;
            }
        }
     public class ConversionOptions : INotifyPropertyChanged
        {
            private string _outputPath;
            public string OutputPath
            {
                get { return _outputPath;}
                set
                {
                    _outputPath = value;
                    OnPropertyChanged(o => o.OutputPath);
                }
            }
    
            private string _blogName;
            public string BlogName
            {
                get { return _blogName;}
                set
                {
                    _blogName = value;
                    OnPropertyChanged(o => o.BlogName);
                }
            }
    
            private string _secretWord;
            public string SecretWord
            {
                get { return _secretWord; }
                set
                {
                    _secretWord = value;
                    OnPropertyChanged(o => o.SecretWord);
                }
            }
    
    
            protected virtual void OnPropertyChanged<R>(Expression<Func<ConversionOptions, R>> propertyExpr)
            {
                OnPropertyChanged(this.GetPropertySymbol(propertyExpr));
            }
    
            protected virtual void OnPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
    
           
            public event PropertyChangedEventHandler PropertyChanged;
        }
    复制代码

    注释:这里还有更多参考信息,您可以在这里了解更加清楚:

    wpf MVVM

    ingebrigtsen

    MSDN

    dorony blogs

    本人总结出比较简洁一点的:

    public static class SymbolExtensions
        {
            public static string GetPropertySymbol<R>(this PropertyChangedEventHandler eventHandler, Expression<Func<R>> expr)
            {
                return ((MemberExpression)expr.Body).Member.Name;
            }
        }
    
        class ViewModelBase: INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
    
            private void OnPropertyChanged(string strPropertyName)
            {
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(strPropertyName));
            }
    
            protected virtual void OnPropertyChanged<R>(Expression<Func<R>> propertyExpr)
            {
                OnPropertyChanged(this.PropertyChanged.GetPropertySymbol(propertyExpr));
            }
        }

    使用,继承ViewModelBase

    class vm_MainWindow : ViewModelBase
        {
            public vm_MainWindow()
            {
             
                
            }
    
            #region 属性
            private string _StateTxt;
    
            public string StateTxt
            {
                get { return _StateTxt; }
                set
                {
                    _StateTxt = value;
                    OnPropertyChanged(()=>StateTxt);
                }
            }
    
    
            private ObservableCollection<Student> _listStudent;
    
            public ObservableCollection<Student> listStudent
            {
                get { return _listStudent; }
                set
                {
                    _listStudent = value;
                    OnPropertyChanged(()=>listStudent);
                }
            }
            #endregion
    
    
        }
  • 相关阅读:
    C#使用结构体,输入5个人的学号,姓名,分数,按照成绩高低排列打印出来
    数据库考试试题
    数据库存储系统应用,超市小票系统
    数据库变量与语句
    练习:C#---类(身份证号截取生日、验证邮箱、DateTime)
    Chapter 3. 类---String类、Math类、Datetime类
    练习:C#---for循环(整数和、阶乘、楼梯)
    Chapter 2 C#语句---异常语句
    Chapter 2. C#语句---循环语句
    Chapter 2 C#语句---选择语句
  • 原文地址:https://www.cnblogs.com/lsgsanxiao/p/10804207.html
Copyright © 2011-2022 走看看