WPF MVVM之INotifyPropertyChanged接口的几种实现方式

序言

       借助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

你可能感兴趣的:(property)