1.Overview

基于MVVM实现一段绑定大伙都不陌生,Binding是wpf整个体系中最核心的对象之一这里就来解读一下我花了纯两周时间有哪些秘密。这里我先提出几个问题应该是大家感兴趣的,如下:

(1)INotifyPropertyChanged是如何被加载、触发的(Binding如何完成数据更新的)?

(2)为什么需要开发者手动实现INotifyPropertyChanged接口来为每个成员实现数据通知,为什么不集成在wpf框架里?

(3)藏在WPF体系里的观察者模式在哪里?

2.Detail

想了解以上问题,我们先补充以下前置知识点。

我们带着以上几个问题来看本文的后续内容,首先我们通过下面这张图来了解绑定的过程。

根据以上过程我们可以基于MVVM模式下,在Xaml中写出这样的语句来表示绑定。

  1. <TextBoxName="mytextbox"Height="25"Width="150"Text="{BindingPath=Name,Mode=**TwoWay**,UpdateSourceTrigger=**PropertyChanged**}"></TextBox>

那么如果把他转换成c#代码,将会是如下表示。

  1. public string BeachName{ get; set; }
  2. private void Test()
  3. {
  4. BeachName="BikiniBeach";
  5. TextBoxtextBox = newTextBox();
  6. textBox.Name = "myTextBox";
  7. Binding binding = new Binding();
  8. binding.Source = BeachName;
  9. binding.Path = new PropertyPath("BeachName");
  10. textBox.SetBinding(TextBox.TextProperty, binding);
  11. }

(1-1)

上面这段代码,包含了两个关键对象Textbox和Binding它们里面大有文章首先我们逐个拆解这两个对象里都有什么。

Textbox

在(1-1)的代码中初始化一个Textbox对象,它会创建一个依赖属性TextProperty用于绑定要素之一。

  1. public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof (Text), typeof (string), typeof (TextBox), (PropertyMetadata) new FrameworkPropertyMetadata((object) string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, new PropertyChangedCallback(TextBox.OnTextPropertyChanged), new CoerceValueCallback(TextBox.CoerceText), true, UpdateSourceTrigger.LostFocus));

Binding

当我们在日常开发实现绑定过程当中,WPF的体系会默默帮你创建Binding对象,这里我们来看看Binding包含了哪些定义(为了观看体验删除了大部分不相关代码)。

  1. namespace System.Windows.Data
  2. {
  3. public class Binding : BindingBase
  4. {
  5. //....其它代码省略
  6. public static void AddSourceUpdatedHandler(
  7. DependencyObject element,
  8. EventHandler<DataTransferEventArgs> handler)
  9. {
  10. UIElement.AddHandler(element, Binding.SourceUpdatedEvent, (Delegate) handler);
  11. }
  12. public static void RemoveSourceUpdatedHandler(
  13. DependencyObject element,
  14. EventHandler<DataTransferEventArgs> handler)
  15. {
  16. UIElement.RemoveHandler(element, Binding.SourceUpdatedEvent, (Delegate) handler);
  17. }
  18. public static void AddTargetUpdatedHandler(
  19. DependencyObject element,
  20. EventHandler<DataTransferEventArgs> handler)
  21. {
  22. UIElement.AddHandler(element, Binding.TargetUpdatedEvent, (Delegate) handler);
  23. }
  24. public static void RemoveTargetUpdatedHandler(
  25. DependencyObject element,
  26. EventHandler<DataTransferEventArgs> handler)
  27. {
  28. UIElement.RemoveHandler(element, Binding.TargetUpdatedEvent, (Delegate) handler);
  29. }
  30. public Binding(string path)
  31. {
  32. if (path == null)
  33. return;
  34. if (Dispatcher.CurrentDispatcher == null)
  35. throw new InvalidOperationException();
  36. this.Path = new PropertyPath(path, (object[]) null);
  37. }
  38. public PropertyPath Path
  39. {
  40. get => this._ppath;
  41. set
  42. {
  43. this.CheckSealed();
  44. this._ppath = value;
  45. this._attachedPropertiesInPath = -1;
  46. this.ClearFlag(BindingBase.BindingFlags.PathGeneratedInternally);
  47. if (this._ppath == null || !this._ppath.StartsWithStaticProperty)
  48. return;
  49. if (this._sourceInUse == Binding.SourceProperties.None || this._sourceInUse == Binding.SourceProperties.StaticSource || FrameworkCompatibilityPreferences.TargetsDesktop_V4_0)
  50. this.SourceReference = Binding.StaticSourceRef;
  51. else
  52. throw new InvalidOperationException(SR.Get("BindingConflict", (object) Binding.SourceProperties.StaticSource, (object) this._sourceInUse));
  53. }
  54. }
  55. [DefaultValue(BindingMode.Default)]
  56. public BindingMode Mode
  57. {
  58. get
  59. {
  60. switch (this.GetFlagsWithinMask(BindingBase.BindingFlags.PropagationMask))
  61. {
  62. case BindingBase.BindingFlags.OneTime:
  63. return BindingMode.OneTime;
  64. case BindingBase.BindingFlags.OneWay:
  65. return BindingMode.OneWay;
  66. case BindingBase.BindingFlags.OneWayToSource:
  67. return BindingMode.OneWayToSource;
  68. case BindingBase.BindingFlags.TwoWay:
  69. return BindingMode.TwoWay;
  70. case BindingBase.BindingFlags.PropDefault:
  71. return BindingMode.Default;
  72. default:
  73. Invariant.Assert(false, "Unexpected BindingMode value");
  74. return BindingMode.TwoWay;
  75. }
  76. }
  77. set
  78. {
  79. this.CheckSealed();
  80. BindingBase.BindingFlags flags = BindingBase.FlagsFrom(value);
  81. if (flags == BindingBase.BindingFlags.IllegalInput)
  82. throw new InvalidEnumArgumentException(nameof (value), (int) value, typeof (BindingMode));
  83. this.ChangeFlagsWithinMask(BindingBase.BindingFlags.PropagationMask, flags);
  84. }
  85. }
  86. [DefaultValue(UpdateSourceTrigger.Default)]
  87. public UpdateSourceTrigger UpdateSourceTrigger
  88. {
  89. get
  90. {
  91. switch (this.GetFlagsWithinMask(BindingBase.BindingFlags.UpdateDefault))
  92. {
  93. case BindingBase.BindingFlags.OneTime:
  94. return UpdateSourceTrigger.PropertyChanged;
  95. case BindingBase.BindingFlags.UpdateOnLostFocus:
  96. return UpdateSourceTrigger.LostFocus;
  97. case BindingBase.BindingFlags.UpdateExplicitly:
  98. return UpdateSourceTrigger.Explicit;
  99. case BindingBase.BindingFlags.UpdateDefault:
  100. return UpdateSourceTrigger.Default;
  101. default:
  102. Invariant.Assert(false, "Unexpected UpdateSourceTrigger value");
  103. return UpdateSourceTrigger.Default;
  104. }
  105. }
  106. set
  107. {
  108. this.CheckSealed();
  109. BindingBase.BindingFlags flags = BindingBase.FlagsFrom(value);
  110. if (flags == BindingBase.BindingFlags.IllegalInput)
  111. throw new InvalidEnumArgumentException(nameof (value), (int) value, typeof (UpdateSourceTrigger));
  112. this.ChangeFlagsWithinMask(BindingBase.BindingFlags.UpdateDefault, flags);
  113. }
  114. }
  115. [DefaultValue(false)]
  116. public bool NotifyOnSourceUpdated
  117. {
  118. get => this.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated);
  119. set
  120. {
  121. if (this.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated) == value)
  122. return;
  123. this.CheckSealed();
  124. this.ChangeFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated, value);
  125. }
  126. }
  127. [DefaultValue(false)]
  128. public bool NotifyOnTargetUpdated
  129. {
  130. get => this.TestFlag(BindingBase.BindingFlags.NotifyOnTargetUpdated);
  131. set
  132. {
  133. if (this.TestFlag(BindingBase.BindingFlags.NotifyOnTargetUpdated) == value)
  134. return;
  135. this.CheckSealed();
  136. this.ChangeFlag(BindingBase.BindingFlags.NotifyOnTargetUpdated, value);
  137. }
  138. }
  139. [DefaultValue(null)]
  140. public IValueConverter Converter
  141. {
  142. get => (IValueConverter) this.GetValue(BindingBase.Feature.Converter, (object) null);
  143. set
  144. {
  145. this.CheckSealed();
  146. this.SetValue(BindingBase.Feature.Converter, (object) value, (object) null);
  147. }
  148. }
  149. public object Source
  150. {
  151. get
  152. {
  153. WeakReference<object> weakReference = (WeakReference<object>) this.GetValue(BindingBase.Feature.ObjectSource, (object) null);
  154. if (weakReference == null)
  155. return (object) null;
  156. object target;
  157. return !weakReference.TryGetTarget(out target) ? (object) null : target;
  158. }
  159. set
  160. {
  161. this.CheckSealed();
  162. if (this._sourceInUse == Binding.SourceProperties.None || this._sourceInUse == Binding.SourceProperties.Source)
  163. {
  164. if (value != DependencyProperty.UnsetValue)
  165. {
  166. this.SetValue(BindingBase.Feature.ObjectSource, (object) new WeakReference<object>(value));
  167. this.SourceReference = (ObjectRef) new ExplicitObjectRef(value);
  168. }
  169. else
  170. {
  171. this.ClearValue(BindingBase.Feature.ObjectSource);
  172. this.SourceReference = (ObjectRef) null;
  173. }
  174. }
  175. else
  176. throw new InvalidOperationException(SR.Get("BindingConflict", (object) Binding.SourceProperties.Source, (object) this._sourceInUse));
  177. }
  178. }
  179. internal override BindingExpressionBase CreateBindingExpressionOverride(
  180. DependencyObject target,
  181. DependencyProperty dp,
  182. BindingExpressionBase owner)
  183. {
  184. return (BindingExpressionBase) BindingExpression.CreateBindingExpression(target, dp, this, owner);
  185. }
  186. }
  187. }

Binding对象继承自BindingBase,在Binding类中我们可以看到CreateBindingExpressionOverride这个方法,这个方法来自父类BindingBase。代码中的BindingExpression是“绑定表达式”的意思,在CreateBindingExpression中入参完美的阐述了绑定关系;

  1. internal override BindingExpressionBase CreateBindingExpressionOverride(
  2. DependencyObject target,
  3. DependencyProperty dp,
  4. BindingExpressionBase owner)
  5. {
  6. return (BindingExpressionBase) BindingExpression.CreateBindingExpression(target, dp, this, owner);
  7. }
  8. internal static BindingExpression CreateBindingExpression(
  9. DependencyObject d,
  10. DependencyProperty dp,
  11. Binding binding,
  12. BindingExpressionBase parent)
  13. {
  14. if (dp.GetMetadata(d.DependencyObjectType) is FrameworkPropertyMetadata metadata && !metadata.IsDataBindingAllowed || dp.ReadOnly)
  15. throw new ArgumentException(System.Windows.SR.Get("PropertyNotBindable", (object) dp.Name), nameof (dp));
  16. BindingExpression bindingExpression = new BindingExpression(binding, parent);
  17. bindingExpression.ResolvePropertyDefaultSettings(binding.Mode, binding.UpdateSourceTrigger, metadata);
  18. if (bindingExpression.IsReflective && binding.XPath == null && (binding.Path == null || string.IsNullOrEmpty(binding.Path.Path)))
  19. throw new InvalidOperationException(System.Windows.SR.Get("TwoWayBindingNeedsPath"));
  20. return bindingExpression;
  21. }

(1)DependencyObject,是所有控件的基类这里我们在当前环境中可以理解为Textbox。

(2)DependencyProperty,是我们要绑定的控件中的TextProperty依赖属性。

(3)Binding,表达了数据源、绑定目标、绑定模式、更新通知触发类型等信息。

创建binding对象,建立绑定表达式CreateBindingExpression将依赖属性和控件、绑定对象关联起来->BindingExpression该方法将Path传给 TraceData.Trace追踪对象。在Binding继承的BindingBase.cs中实现了CreateBindingExpression(创建绑定表达式,它的作用就是用来“描述”绑定的整个过程)

[BindingExpression作用-1]

该对象提供了绑定更新的机制,UpdateSourceTrigger.Explicit 模式用来控制源对象的更新时机。

(1)调用 BindingExpression.UpdateSource()和 UpdateTarget( )方法,触发立即刷新行为。

(2)获取 BindingExpression对象,需要使用 GetBindingExpression( )方法。

  1. BindingExpiression binding =
  2. txtFontSize.GetBindingExpression(TextBox ,TextProperty);
  3. binding.UpdateSource();

要完全控制源对象的更新时机,可选择 UpdateSourceTrigger.ExpUdt 模式。如果在文本框示 例中使用这种方法,当文本框失去焦点后不会发生任何事情 反而,由您编写代码手动触发更 新。例如,可添加 Apply 按钮,调用 BindingExpression.UpdateSource()方法,触发立即刷新行为并更新字体尺寸。 当然,在调用 BindingExpressiorLUpdateSource( )之前 ,需要 一 种方法 来获取 BindingExpression 对象。BindingExpressicm 对象仅是将两项内容封装到一起的较小组装包,这 两项内容是:己经学习过的 Binding 对象(通过 BindingExpression.ParentBinding 属性提供)和由 源绑定的对象(BindingExpression.Dataltem)a 此外,BindingExpression 对象为触发立即更新绑定 的-部分提供了两个方法:UpdateSource( )和 UpdateTarget( )方法, 为联取 BindingExpressiori 对象,需要使用 GetBindingExpression( )方法,并传入具有绑定的 目标属性,每个元素都从 FrameworkEkment 类继承了该方法。

可为每个属性引发事件。对于这种情况,事件必须以 的形式迸行命 名(如 UnitCostChanged)当属性变化时,由您负责引发事件。 可实现 System.ComponentModel.INotifyPropertyChanged 接口,该接口需要名为 PropertyChanged 的事件。无论何时属性发生变化,都必须引发 PropertyChanged 事件,并 且通过将属性名称作为字符串提供来指示哪个属性发生了变化。当属性发生变化时,仍 由您负责引发事件,但不必为每个属性定义单独的事件& 第一种方法依赖于 WPF 的依赖项属性基础架构,而第二种和第三种方法依赖于事件,通 常,当创建数据对象时,会使用第三种方法。对于非元素类而言,这是最简单的选择。实际上,还可使用另一种方法如果怀疑绑定对象已经发生变化,并且绑定对象不支持任 何恰当方 式的更改通知,这时可检索 BindingExpression 对象(使用 FrameworkElement. GetBmdingExpression()方法),并调用 BindingExpresskm.UpdateTarget()方法来触发更新, 这是最憨的解决方案。

[BindingExpression作用-2]

BindingExpression继承自BindingExpressionBase除了表述绑定关系以外,还创建了BindingWorker对象(下面为关键代码)这里所要讲的就是INotifyPropertyChanged是如何被加载、触发的。

  1. private BindingWorker _worker;
  2. private void CreateWorker()
  3. {
  4. Invariant.Assert(this.Worker == null, "duplicate call to CreateWorker");
  5. this._worker = (BindingWorker) new ClrBindingWorker(this, this.Engine);
  6. }

上面代码将_worker初始化为ClrBindingWorker,它里面又包含PropertyPathWorker对象,PropertyPathWorker这个对象中有一个方法UpdateSourceValueState,它会从上层引用中拿到ViewModel的引用(引用会逐层从Binding类的层面逐层传递进来)然后会判断这个ViewModel是否继承了INotifyPropertyChanged如果继承了则找到public event PropertyChangedEventHandler PropertyChanged;的引用并进行管理。

  1. else if (newO is INotifyPropertyChanged source15)
  2. PropertyChangedEventManager.AddHandler(source15, new EventHandler<PropertyChangedEventArgs>(this.OnPropertyChanged), this.SVI[k].propertyName);

ViewModel.PropertyChangedEventHandler的我们开发者定义好的通知事件,添加进入到PropertyChangedEventManager中进行管理,这个时候我们在给ViewModel里的变量Set值能通知界面更改就这么来的;下面为PropertyChangedEventManager.cs部分源码(这里的Manager类似于观察者模式)。

  1. //将ViewModel里的PropertyChangedEventHandler PropertyChanged;添加监听
  2. private void AddListener(
  3. INotifyPropertyChanged source,
  4. string propertyName,
  5. IWeakEventListener listener,
  6. EventHandler<PropertyChangedEventArgs> handler)
  7. {
  8. using (this.WriteLock)
  9. {
  10. HybridDictionary hybridDictionary = (HybridDictionary) this[(object) source];
  11. if (hybridDictionary == null)
  12. {
  13. hybridDictionary = new HybridDictionary(true);
  14. this[(object) source] = (object) hybridDictionary;
  15. this.StartListening((object) source);
  16. }
  17. WeakEventManager.ListenerList list = (WeakEventManager.ListenerList) hybridDictionary[(object) propertyName];
  18. if (list == null)
  19. {
  20. list = (WeakEventManager.ListenerList) new WeakEventManager.ListenerList<PropertyChangedEventArgs>();
  21. hybridDictionary[(object) propertyName] = (object) list;
  22. }
  23. if (WeakEventManager.ListenerList.PrepareForWriting(ref list))
  24. hybridDictionary[(object) propertyName] = (object) list;
  25. if (handler != null)
  26. list.AddHandler((Delegate) handler);
  27. else
  28. list.Add(listener);
  29. hybridDictionary.Remove((object) PropertyChangedEventManager.AllListenersKey);
  30. this._proposedAllListenersList = (WeakEventManager.ListenerList) null;
  31. this.ScheduleCleanup();
  32. }
  33. }
  34. //这里每次在ViewModel里给变量Set值的之后就是通过OnPropertyChanged通知界面更改值,sender是ViewModel对象
  35. private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
  36. {
  37. string propertyName = args.PropertyName;
  38. WeakEventManager.ListenerList list;
  39. using (this.ReadLock)
  40. {
  41. HybridDictionary hybridDictionary = (HybridDictionary) this[sender];
  42. if (hybridDictionary == null)
  43. list = WeakEventManager.ListenerList.Empty;
  44. else if (!string.IsNullOrEmpty(propertyName))
  45. {
  46. WeakEventManager.ListenerList<PropertyChangedEventArgs> listenerList1 = (WeakEventManager.ListenerList<PropertyChangedEventArgs>) hybridDictionary[(object) propertyName];
  47. WeakEventManager.ListenerList<PropertyChangedEventArgs> listenerList2 = (WeakEventManager.ListenerList<PropertyChangedEventArgs>) hybridDictionary[(object) string.Empty];
  48. if (listenerList2 == null)
  49. list = listenerList1 == null ? WeakEventManager.ListenerList.Empty : (WeakEventManager.ListenerList) listenerList1;
  50. else if (listenerList1 != null)
  51. {
  52. list = (WeakEventManager.ListenerList) new WeakEventManager.ListenerList<PropertyChangedEventArgs>(listenerList1.Count + listenerList2.Count);
  53. int index1 = 0;
  54. for (int count = listenerList1.Count; index1 < count; ++index1)
  55. list.Add(listenerList1.GetListener(index1));
  56. int index2 = 0;
  57. for (int count = listenerList2.Count; index2 < count; ++index2)
  58. list.Add(listenerList2.GetListener(index2));
  59. }
  60. else
  61. list = (WeakEventManager.ListenerList) listenerList2;
  62. }
  63. else
  64. {
  65. list = (WeakEventManager.ListenerList) hybridDictionary[(object) PropertyChangedEventManager.AllListenersKey];
  66. if (list == null)
  67. {
  68. int capacity = 0;
  69. foreach (DictionaryEntry dictionaryEntry in hybridDictionary)
  70. capacity += ((WeakEventManager.ListenerList) dictionaryEntry.Value).Count;
  71. list = (WeakEventManager.ListenerList) new WeakEventManager.ListenerList<PropertyChangedEventArgs>(capacity);
  72. foreach (DictionaryEntry dictionaryEntry in hybridDictionary)
  73. {
  74. WeakEventManager.ListenerList listenerList = (WeakEventManager.ListenerList) dictionaryEntry.Value;
  75. int index = 0;
  76. for (int count = listenerList.Count; index < count; ++index)
  77. list.Add(listenerList.GetListener(index));
  78. }
  79. this._proposedAllListenersList = list;
  80. }
  81. }
  82. list.BeginUse();
  83. }
  84. try
  85. {
  86. this.DeliverEventToList(sender, (EventArgs) args, list);
  87. }
  88. finally
  89. {
  90. list.EndUse();
  91. }
  92. if (this._proposedAllListenersList != list)
  93. return;
  94. using (this.WriteLock)
  95. {
  96. if (this._proposedAllListenersList != list)
  97. return;
  98. HybridDictionary hybridDictionary = (HybridDictionary) this[sender];
  99. if (hybridDictionary != null)
  100. hybridDictionary[(object) PropertyChangedEventManager.AllListenersKey] = (object) list;
  101. this._proposedAllListenersList = (WeakEventManager.ListenerList) null;
  102. }
  103. }

[BindingExpression作用-3]

这里主要讲述,如果直接在文本框内直接修改数据Binding是如何更新通知的(View->ViewModel)。

1.创建Binding对象,建立绑定表达式CreateBindingExpression将依赖属性和控件、绑定对象关联起来->BindingExpression该方法将Path传给 TraceData.Trace追踪Path。

2.手动在Textbox中输入内容则会被控件中的OnPreviewTextInput事件捕捉到,最后由BindingExpressionBase.OnPreviewTextInput触发Drity方法。

[特别分享:这里的Dirty命名我觉得很有造诣,这里分享一下我的理解Dirty直接翻译为‘脏’这个字如何去理解,举例:下雨天雨点落在了车窗玻璃上,这时候雨刷器把落在玻璃上的雨点视为‘脏’东西然后雨刷器刷一下把所有雨点清理干净了。借喻到代码中就是当有数据需要更新调用Dirty方法解决所有的更新需求。]

  1. internal void Dirty()
  2. {
  3. if (ShouldReactToDirty())
  4. {
  5. NeedsUpdate = true;
  6. if (!HasValue(Feature.Timer))
  7. {
  8. ProcessDirty();
  9. }
  10. else
  11. {
  12. // restart the timer
  13. DispatcherTimer timer = (DispatcherTimer)GetValue(Feature.Timer, null);
  14. timer.Stop();
  15. timer.Start();
  16. }
  17. NotifyCommitManager();
  18. }
  19. }

Drity方法会检测是否有数据改动没有改动则退出更新机制。如果在绑定表达式中用了Delay属性,则会触发BindingExpressionBase中的DispatcherTimer来达到数据延迟更新的效果。可见每创建一个绑定表达式里都会包含一个定时器只是大部分时间不会启动而已。内部会有bool的标记来判断更新过程是否开始或结束。

  1. private void DetermineEffectiveUpdateBehavior()
  2. {
  3. if (!this.IsReflective)
  4. return;
  5. for (BindingExpressionBase bindingExpressionBase = this.ParentBindingExpressionBase; bindingExpressionBase != null; bindingExpressionBase = bindingExpressionBase.ParentBindingExpressionBase)
  6. {
  7. if (bindingExpressionBase is MultiBindingExpression)
  8. return;
  9. }
  10. int delay = this.ParentBindingBase.Delay;
  11. if (delay <= 0 || !this.IsUpdateOnPropertyChanged)
  12. return;
  13. DispatcherTimer dispatcherTimer = new DispatcherTimer();
  14. this.SetValue(BindingExpressionBase.Feature.Timer, (object) dispatcherTimer);
  15. //这里的Interval就是根据我们在设置Binding对象Delay属性来设置的。如果写Delay=1000;那么就是1秒后触发更新
  16. dispatcherTimer.Interval = TimeSpan.FromMilliseconds((double) delay);
  17. dispatcherTimer.Tick += new EventHandler(this.OnTimerTick);
  18. }

3.这时候访问依赖属性Text的内容去修改绑定在ViewModel的属性BindingExpression.UpdateSource(object value)。

4.BindingExpressionBase.UpdateValue()里的object rawProposedValue = this.GetRawProposedValue();会去拿到依赖属性的值这时候取到的内容是没有被验证是否合法的内容,然后会做两件事情

(1)会判断值是否合法能否通过验证规则。

(2)如果在绑定表达式里写了Convert转换器,则进行值转换。

完成以上两步的值将会object obj = this.UpdateSource(convertedValue)来触发更新;最终由依赖属性中PropertyMetadata注册的PropertyChangedCallback来落实值的修改。

  1. internal bool UpdateValue()
  2. {
  3. ValidationError oldValidationError = BaseValidationError;
  4. if (StatusInternal == BindingStatusInternal.UpdateSourceError)
  5. SetStatus(BindingStatusInternal.Active);
  6. object value = GetRawProposedValue();
  7. if (!Validate(value, ValidationStep.RawProposedValue))
  8. return false;
  9. value = ConvertProposedValue(value);
  10. if (!Validate(value, ValidationStep.ConvertedProposedValue))
  11. return false;
  12. value = UpdateSource(value);
  13. if (!Validate(value, ValidationStep.UpdatedValue))
  14. return false;
  15. value = CommitSource(value);
  16. if (!Validate(value, ValidationStep.CommittedValue))
  17. return false;
  18. if (BaseValidationError == oldValidationError)
  19. {
  20. // the binding is now valid - remove the old error
  21. UpdateValidationError(null);
  22. }
  23. EndSourceUpdate();
  24. NotifyCommitManager();
  25. return !HasValue(Feature.ValidationError);
  26. }

看到这里大家应该会明白设计者为什么不把ViewModel的每个字段默认集数据通知机制,我个人的理解是数据通知会带来一定的性能损耗所以开放给开发者“按需”添加通知的成员。

3.Reference

解读WPF中的Binding的更多相关文章

  1. 【转】WPF中的Binding技巧(二)

    WPF中的Binding技巧(二)     接上篇, 我们来看一看Elementname,Source,RelativeSource 三种绑定的方式 1.ElementName顾名思义就是根据Ui元素 ...

  2. C# wpf中关于binding的converter无效的情况

    最近碰到bingding设置了convert转换无效的问题.困扰了我好久.这里记录分析一下. 先说下现象 我把TextBox的text属性  绑定到了对应的 convert.代码如下 希望吧pd_no ...

  3. Binding在WPF中的使用

    闲来无事,不想打DOTA,在这里小小研究下wpf中关于Binding的东西. 咯咯 在我们印象中,Binding的意思是“绑定”,这个“绑”大概取自于Bind这个单词吧,这么理解的话就是以音译英了,没 ...

  4. 【转】WPF中Binding的技巧(一)

    WPF中Binding的技巧(一)   在WPF应用的开发过程中Binding是一个非常重要的部分. 在实际开发过程中Binding的不同种写法达到的效果相同但事实是存在很大区别的. 这里将实际中碰到 ...

  5. WPF binding<一> Data Binding在WPF中的地位

    在代码中看到 <Image Source="{Binding ElementName=LBoxImages, Path=SelectedItem.Source}" /> ...

  6. WPF中Binding使用StringFormat格式化字符串方法

    原文:WPF中Binding使用StringFormat格式化字符串方法 货币格式 <TextBlock Text="{Binding Price, StringFormat={}{0 ...

  7. 整理:WPF中Binding的几种写法

    原文:整理:WPF中Binding的几种写法 目的:整理WPF中Bind的写法 <!--绑定到DataContext--> <Button Content="{Bindin ...

  8. MVVM模式和在WPF中的实现(二)数据绑定

    MVVM模式解析和在WPF中的实现(二) 数据绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  9. WPF入门教程系列十八——WPF中的数据绑定(四)

    六.排序 如果想以特定的方式对数据进行排序,可以绑定到 CollectionViewSource,而不是直接绑定到 ObjectDataProvider.CollectionViewSource 则会 ...

随机推荐

  1. Joseph(hdu1443)

    Joseph Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Sub ...

  2. RASP Runtime Application Self-protection 运行时应用自我保护 介绍及优缺点

    RASP 介绍 Runtime Application Self-protection 运行时应用自我保护 [图源:绿盟科技] 概念 Gartner (著名信息技术研究和分析厂商) 在2014年提出了 ...

  3. 警惕!PHP、Node、Ruby 和 Python 应用,漏洞还没结束!

    12 月 10 日凌晨,Apache 开源项目 Log4j2 的远程代码执行漏洞细节被公开,作为当前全球使用最广泛的 java 日志框架之一.该漏洞影响着很多全球使用量前列的开源组件,如 Apache ...

  4. 洛谷1052——过河(DP+状态压缩)

    题目描述 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数 ...

  5. rabbitmq集群和镜像队列

    Rabbitmq集群和镜像队列 1引言 1.1编写目的 2 原理和使用 2.1镜像队列原理 2.1.1 原理 默认的一个rabbitmq中的queue是在一个node上的,至于在那个node上取决于c ...

  6. HTML多端适应 响应式布局案例

    HTML布局: <div id="one"> <div class="aa"></div> <div class=&q ...

  7. CSS基础6之盒子模型1

    盒子概述 以下是盒子模型的一个图形解释 一.内边距(填充) 属性有: (1) padding  设置所有内边距 (2) padding-top  设置上边距 (3) padding-left 设置左边 ...

  8. Lomsat gelral

    题目描述 You are given a rooted tree with root in vertex 11 . Each vertex is coloured in some colour. Le ...

  9. PowerShell 教程

    随笔分类 - 教程 转载自:https://www.cnblogs.com/XiaoCY/category/1065141.html PowerShell 管道符之Where-Object的使用方法 ...

  10. List<FieldModelBase> 转 DataTable

    // List<FieldModelBase> 转 DataTable private DataTable ListToDataTable(List<FieldModelBase&g ...