WPF学习总结1:INotifyPropertyChanged接口的作用

在代码中经常见到这个接口,它里面有什么?它的作用是什么?它和依赖属性有什么关系?

下面就来总结回答这三个问题。

1.这个INotifyPropertyChanged接口里就一个PropertyChanged的event,这个接口其实是从.net 2.0就引入进来的,用它实现观察者模式很是方便。

WPF学习总结1:INotifyPropertyChanged接口的作用
#region Assembly System.dll, v4.0.0.0

// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\System.dll

#endregion



namespace System.ComponentModel

{

    // Summary:

    //     Notifies clients that a property value has changed.

    public interface INotifyPropertyChanged

    {

        // Summary:

        //     Occurs when a property value changes.

        event PropertyChangedEventHandler PropertyChanged;

    }

}
View Code

2.它的作用是什么?

首先创建一个不用这个接口的例子。

创建一个Employee.cs类。

WPF学习总结1:INotifyPropertyChanged接口的作用
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;



namespace WpfAppLearning

{

    public class Employee



    {

        private string _name;

        public string Name 

        {

            get {

                return _name;

            }

            set

            {

                _name = value;



            }

        }

    }

}
View Code

 再创建一个MainWindow.xaml

WPF学习总结1:INotifyPropertyChanged接口的作用
<Window x:Class="WpfAppLearning.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="MainWindow" Height="350" Width="525">

    <Grid>

        <TextBox Name="txt1" HorizontalAlignment="Left" Height="23" Margin="148,74,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

        <Button Content="Button" HorizontalAlignment="Left" Margin="148,143,0,0" VerticalAlignment="Top" Width="75"/>



    </Grid>

</Window>
View Code

在MainWindow.xaml.cs里将Employee的Name属性和TextBox的Text属性绑定起来。

WPF学习总结1:INotifyPropertyChanged接口的作用
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;



namespace WpfAppLearning

{

    /// <summary>

    /// Interaction logic for MainWindow.xaml

    /// </summary>

    public partial class MainWindow : Window

    {

        public MainWindow()

        {

            InitializeComponent();



            //新建一个员工,并给员工姓名赋初值

            Employee employee = new Employee();

            employee.Name = "Tom";



            //创建绑定

            Binding bind = new Binding();

            bind.Source = employee;

            bind.Path = new PropertyPath("Name");



            //设置绑定

            this.txt1.SetBinding(TextBox.TextProperty, bind);



            //修改员工姓名以后

            employee.Name = "Bob";

        }

    }

}
View Code

运行起来,效果如下:
WPF学习总结1:INotifyPropertyChanged接口的作用

也就是说,给textbox绑定了数据源Employee对象之后,我修改了Employee对象的Name属性,但在界面上并没有显示出来,界面上显示的还是原来的初始值。

这时,可以让PropertyChanged登场了,其他都不动,只重新修改Employee.cs代码:

WPF学习总结1:INotifyPropertyChanged接口的作用
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.ComponentModel;



namespace WpfAppLearning

{

    public class Employee : INotifyPropertyChanged

    {

        public event PropertyChangedEventHandler PropertyChanged;



        private string _name;

        public string Name

        {

            get

            {

                return _name;

            }

            set

            {

                _name = value;

                if (this.PropertyChanged != null)

                {

                    this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));

                }

            }

        }

    }

}
View Code

运行如下:
WPF学习总结1:INotifyPropertyChanged接口的作用

可见,只要实现了这个接口,在Name属性值改变时激发一下PropertyChanged这个event,就能使binding得到变更通知了。

显然,在创建Binding对象并将它作为数据源绑定到TextBox控件时,TextBox控件自动订阅了这个PropertyChanged event。

但它是在哪里订阅的呢?很想知道,于是...

在Reflector里查看Binding.cs的代码,从它的构造函数,到Source及Path属性的代码中都找不到订阅该event的踪影。

WPF学习总结1:INotifyPropertyChanged接口的作用
    public class Binding : BindingBase

    {

        public Binding()

        {

        }



        public object Source

        {

            get

            {

                WeakReference<object> weakReference = (WeakReference<object>)base.GetValue(BindingBase.Feature.ObjectSource, null);

                if (weakReference == null)

                {

                    return null;

                }

                object result;

                if (!weakReference.TryGetTarget(out result))

                {

                    return null;

                }

                return result;

            }

            set

            {

                base.CheckSealed();

                if (this._sourceInUse != Binding.SourceProperties.None && this._sourceInUse != Binding.SourceProperties.Source)

                {

                    throw new InvalidOperationException(SR.Get("BindingConflict", new object[]

                    {

                        Binding.SourceProperties.Source,

                        this._sourceInUse

                    }));

                }

                if (value != DependencyProperty.UnsetValue)

                {

                    base.SetValue(BindingBase.Feature.ObjectSource, new WeakReference<object>(value));

                    this.SourceReference = new ExplicitObjectRef(value);

                    return;

                }

                base.ClearValue(BindingBase.Feature.ObjectSource);

                this.SourceReference = null;

            }

        }



        public PropertyPath Path

        {

            [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]

            get

            {

                return this._ppath;

            }

            set

            {

                base.CheckSealed();

                this._ppath = value;

                this._attachedPropertiesInPath = -1;

                base.ClearFlag(BindingBase.BindingFlags.PathGeneratedInternally);

                if (this._ppath == null || !this._ppath.StartsWithStaticProperty)

                {

                    return;

                }

                if (this._sourceInUse == Binding.SourceProperties.None || this._sourceInUse == Binding.SourceProperties.StaticSource)

                {

                    this.SourceReference = Binding.StaticSourceRef;

                    return;

                }

                throw new InvalidOperationException(SR.Get("BindingConflict", new object[]

                {

                    Binding.SourceProperties.StaticSource,

                    this._sourceInUse

                }));

            }

        }

    }
View Code

实际上Binding类中有一个UpdateSourceTrigger属性:

WPF学习总结1:INotifyPropertyChanged接口的作用
public class Binding : BindingBase

{



  [DefaultValue(0)]

  public UpdateSourceTrigger UpdateSourceTrigger

  {

    get

    {

        switch (base.GetFlagsWithinMask(BindingBase.BindingFlags.UpdateOnLostFocus | BindingBase.BindingFlags.UpdateExplicitly))

        {

            case BindingBase.BindingFlags.UpdateOnPropertyChanged:

                return UpdateSourceTrigger.PropertyChanged;



            case BindingBase.BindingFlags.UpdateOnLostFocus:

                return UpdateSourceTrigger.LostFocus;



            case BindingBase.BindingFlags.UpdateExplicitly:

                return UpdateSourceTrigger.Explicit;



            case (BindingBase.BindingFlags.UpdateOnLostFocus | BindingBase.BindingFlags.UpdateExplicitly):

                return UpdateSourceTrigger.Default;

        }

        Invariant.Assert(false, "Unexpected UpdateSourceTrigger value");

        return UpdateSourceTrigger.Default;

    }

    set

    {

        base.CheckSealed();

        BindingBase.BindingFlags flags = BindingBase.FlagsFrom(value);

        if (flags == BindingBase.BindingFlags.IllegalInput)

        {

            throw new InvalidEnumArgumentException("value", (int) value, typeof(UpdateSourceTrigger));

        }

        base.ChangeFlagsWithinMask(BindingBase.BindingFlags.UpdateOnLostFocus | BindingBase.BindingFlags.UpdateExplicitly, flags);

    }

  }

}
View Code
它的类型就是UpdateSourceTrigger枚举,这个枚举类型的值如下:

public enum UpdateSourceTrigger {     Default,     PropertyChanged,     LostFocus,     Explicit }

However, the default value for most dependency properties is System.Windows.Data.UpdateSourceTrigger.PropertyChanged,所以说,创建binding对象时虽然没有设置这个属性,但因为它有默认值,是PropertyChanged,如下:

            //创建绑定
            Binding bind = new Binding();
            bind.Source = employee;
            bind.Path = new PropertyPath("Name");
            //bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; 这句可以省略。

这样看来,似乎已经找到根了,此时Binding对象应该知道了要监听PropertyChanged事件了,但实际上还没有具体订阅上,到底在哪里订阅上PropertyChanged事件的呢?

Debug一下,发现在创建完上面的绑定之后, employee.PropertyChanged为空,可见,此时还未订阅。

employee.PropertyChanged
null

当向下执行完this.txt1.SetBinding(TextBox.TextProperty, bind) 这句后, employee.PropertyChanged不为空了,说明此时已经订阅上了。

employee.PropertyChanged
{Method = {Void OnPropertyChanged(System.Object, System.ComponentModel.PropertyChangedEventArgs)}}
    base {System.MulticastDelegate}: {Method = {Void OnPropertyChanged(System.Object, System.ComponentModel.PropertyChangedEventArgs)}}

 

看看这个this.txt1.SetBinding的Reflector代码,实际上还是调用的BindingOperations类的SetBinding方法。

WPF学习总结1:INotifyPropertyChanged接口的作用
[RuntimeNameProperty("Name"), UsableDuringInitialization(true), StyleTypedProperty(Property="FocusVisualStyle", StyleTargetType=typeof(Control)), XmlLangProperty("Language")]

public class FrameworkElement : UIElement, IFrameworkInputElement, IInputElement, ISupportInitialize, IHaveResources, IQueryAmbient

{

  public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)

  {

    return BindingOperations.SetBinding(this, dp, binding);

  }

}



 
View Code

BindingOperations类的SetBinding方法代码如下:  

WPF学习总结1:INotifyPropertyChanged接口的作用
using MS.Internal.Data;

using System;

using System.Collections;

using System.Collections.Generic;

using System.Collections.ObjectModel;

using System.Runtime;

namespace System.Windows.Data

{

        public static BindingExpressionBase SetBinding(DependencyObject target, DependencyProperty dp, BindingBase binding)

        {

            if (target == null)

            {

                throw new ArgumentNullException("target");

            }

            if (dp == null)

            {

                throw new ArgumentNullException("dp");

            }

            if (binding == null)

            {

                throw new ArgumentNullException("binding");

            }

            BindingExpressionBase bindingExpressionBase = binding.CreateBindingExpression(target, dp);

            target.SetValue(dp, bindingExpressionBase);

            return bindingExpressionBase;

        }

}
View Code

再跳进去查,还是没有发现具体订阅的代码,看来还藏得够隐蔽的!算了,不查了,以后再说。

2013/9/3 补充,之所以找不到显式的事件订阅,可能是使用了weakreference来实现更加高明的订阅,学习中。

 

你可能感兴趣的:(property)