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

posted @ 2012-11-25 11:30  墨梅  阅读(11184)  评论(15编辑  收藏  举报