WPF學習總結1:INotifyPropertyChanged接口的做用

在代碼中常常見到這個接口,它裏面有什麼?它的做用是什麼?它和依賴屬性有什麼關係?app

下面就來總結回答這三個問題。ide

1.這個INotifyPropertyChanged接口裏就一個PropertyChanged的event,這個接口實際上是從.net 2.0就引入進來的,用它實現觀察者模式非常方便。函數

#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.它的做用是什麼?學習

首先建立一個不用這個接口的例子。this

建立一個Employee.cs類。spa

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.net

<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屬性綁定起來。3d

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

運行起來,效果以下:
code

也就是說,給textbox綁定了數據源Employee對象以後,我修改了Employee對象的Name屬性,但在界面上並無顯示出來,界面上顯示的仍是原來的初始值。orm

這時,可讓PropertyChanged登場了,其餘都不動,只從新修改Employee.cs代碼:

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

運行以下:

可見,只要實現了這個接口,在Name屬性值改變時激發一下PropertyChanged這個event,就能使binding獲得變動通知了。

顯然,在建立Binding對象並將它做爲數據源綁定到TextBox控件時,TextBox控件自動訂閱了這個PropertyChanged event。

但它是在哪裏訂閱的呢?很想知道,因而...

在Reflector裏查看Binding.cs的代碼,從它的構造函數,到Source及Path屬性的代碼中都找不到訂閱該event的蹤跡。

    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屬性:

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方法。

[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方法代碼以下:  

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來實現更加高明的訂閱,學習中。

相關文章
相關標籤/搜索