zoukankan      html  css  js  c++  java
  • WPF学习总结1:INotifyPropertyChanged接口的作用

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

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

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

    #region Assembly System.dll, v4.0.0.0
    // C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.5System.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类。

    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

    <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属性绑定起来。

    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

    运行起来,效果如下:

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

    这时,可以让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来实现更加高明的订阅,学习中。

  • 相关阅读:
    人类历史上最智慧的169条警世箴言(句句珠玑,发人深省)
    最负责任的回答
    成大事必须依靠的五种人
    一生的伤痕
    有谁愿意陪我一程
    惜缘
    那朵美丽的格桑花,你是否依然绽放?
    今生今世只等你
    成就一生的15条黄金法则
    遇到困难挫折也不要悲观:每个人生来就是冠军(转)
  • 原文地址:https://www.cnblogs.com/liuzhendong/p/3286278.html
Copyright © 2011-2022 走看看