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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM