WPF|依赖属性SetCurrentValue方法不会使绑定失效, SetValue方法会使绑定失效?是真的吗?

news/2024/10/11 12:10:36/

引言

最近因为一个触发器设置的结果总是不起效果的原因,进一步去了解[依赖属性的优先级](Dependency property value precedence - WPF .NET | Microsoft Learn)。在学习这个的过程中发现对SetCurrentValue一直以来的谬误。

在WPF中依赖属性Dependency property的三个方法SetValue 、SetCurrentValue、ClearValue。

  1. SetCurrentValue 方法用于设置依赖属性的当前值,但不会覆盖该属性的值来源。这意味着,如果属性值是通过绑定、样式或触发器设置的,使用 SetCurrentValue 后,这些设置仍然有效。

然而这很容易让人以为SetValue 方法会使得数据绑定失效。就像先执行了ClearValue 一样。网上我看到的很多文章也这么说,这让我困惑了很久,但我实际操作下来,并非如此,实际上并不是。

为了验证这三个方法,我以设置按钮背景颜色为例,写了一个Demo。

其主要作用如下:

1. myButton绑定默认背景颜色的依赖属性
<Buttonx:Name="MyButton"Width="100"Height="50"Background="{Binding DefaultBackgroundColor,Mode=TwoWay,RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"Content="MyButton" />
 public static readonly DependencyProperty DefaultBackgroundColorProperty =DependencyProperty.Register("DefaultBackgroundColor",typeof(Brush),typeof(MainWindow),new PropertyMetadata(Brushes.Pink,OnDefaultBackgroundColorChanged));private static void OnDefaultBackgroundColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){MessageBox.Show("DefaultBackgroundColor changed");}public static readonly DependencyProperty DefaultForegroundColorProperty =DependencyProperty.Register("DefaultForegroundColor",typeof(Brush),typeof(MainWindow),new PropertyMetadata(Brushes.Gray,OnDefaultForegroundChanged));
2. 按钮1使用SetValue设置myButton的背景颜色属性,并判断绑定表达式是否为空
  private void SetValueChangeBackground_Click(object sender, RoutedEventArgs e){MyButton.SetValue(Button.BackgroundProperty, new SolidColorBrush(Colors.Green));IsBindingExpressionNull();}private void IsBindingExpressionNull()
{ if (MyButton.GetBindingExpression(Button.BackgroundProperty) == null){MessageBox.Show("BindingExpression is null");}
}
3. 按钮2使用SetCurrentValue设置myButton的背景颜色属性
 MyButton.SetCurrentValue(Button.BackgroundProperty, new SolidColorBrush(Colors.Orange));
IsBindingExpressionNull();
4. 按钮3 使用ClearValue 清楚背景颜色属性本地值
 // 清除本地值MyButton.ClearValue(Button.BackgroundProperty);
IsBindingExpressionNull();
5. 按钮4,修改依赖属性
DefaultBackgroundColor = new SolidColorBrush(Colors.LightGreen);
我使用.NET 8 做的Demo的现象如下:
  1. ClearValue执行后,绑定表达式为Null,也就是说ClearValue之后,绑定的数据表达式会被清空
  2. SetValue执行后,按钮颜色正常改变,绑定表达式不为Null。再次执行按钮4 修改依赖属性,按钮背景颜色也可以正常变化,也就是说SetValue之后数据表达式不会被清空,仍然有效。
  3. SetCurrentValue与第2点现象完全一致。

源码

通过查看源码,发现SetCurrentValueSetValue都执行方法SetValueCommon,只是入参coerceWithCurrentValue不同,// SetValue时为false 和SetCurrentValue时为true。

并且推测当SetValue的value入参等于DependencyProperty.UnsetValue时,应当会和ClearValueCommon执行相同的方法。

以下是测试代码,实际测试结果也是如此,此时绑定表达式为Null。

private void SetValueChangeBackgroundUnsetValue_Click(object sender, RoutedEventArgs e){MyButton.SetValue(Button.BackgroundProperty, DependencyProperty.UnsetValue);  //查看源码发现,UnsetValue时才会是清除本地值,并且 ClearValueIsBindingExpressionNull();}
SetValueCommon
        /// <summary>///     The common code shared by all variants of SetValue/// </summary>// Takes metadata from caller because most of them have already retrieved it//  for their own purposes, avoiding the duplicate GetMetadata call.private void SetValueCommon(DependencyProperty  dp,object              value,PropertyMetadata    metadata,bool                coerceWithDeferredReference,bool                coerceWithCurrentValue, // SetValue时为false 和SetCurrentValue时为trueOperationType       operationType,bool                isInternal){if (IsSealed){throw new InvalidOperationException(SR.Get(SRID.SetOnReadOnlyObjectNotAllowed, this));}Expression newExpr = null;DependencySource[] newSources = null;EntryIndex entryIndex = LookupEntry(dp.GlobalIndex);// Treat Unset as a Clearif( value == DependencyProperty.UnsetValue ){Debug.Assert(!coerceWithCurrentValue, "Don't call SetCurrentValue with UnsetValue");// Parameters should have already been validated, so we call//  into the private method to avoid validating again.ClearValueCommon(entryIndex, dp, metadata);return;}// Validate the "value" against the DP.bool isDeferredReference = false;bool newValueHasExpressionMarker = (value == ExpressionInAlternativeStore);// First try to validate the value; only after this validation fails should we// do the more expensive checks (type checks) for the less common scenariosif (!newValueHasExpressionMarker){bool isValidValue = isInternal ? dp.IsValidValueInternal(value) : dp.IsValidValue(value);// for properties of type "object", we have to always check for expression & deferredreferenceif (!isValidValue || dp.IsObjectType){// 2nd most common is expressionnewExpr = value as Expression;if (newExpr != null){// For Expressions, perform additional validation// Make sure Expression is "attachable"if (!newExpr.Attachable){throw new ArgumentException(SR.Get(SRID.SharingNonSharableExpression));}// Check dispatchers of all Sources// CALLBACKnewSources = newExpr.GetSources();ValidateSources(this, newSources, newExpr);}else{// and least common is DeferredReferenceisDeferredReference = (value is DeferredReference);if (!isDeferredReference){if (!isValidValue){// it's not a valid value & it's not an expression, so throwthrow new ArgumentException(SR.Get(SRID.InvalidPropertyValue, value, dp.Name));}}}}}// Get old valueEffectiveValueEntry oldEntry;if (operationType == OperationType.ChangeMutableDefaultValue){oldEntry = new EffectiveValueEntry(dp, BaseValueSourceInternal.Default);oldEntry.Value = value;}else{oldEntry = GetValueEntry(entryIndex, dp, metadata, RequestFlags.RawEntry);}// if there's an expression in some other store, fetch it nowExpression currentExpr =(oldEntry.HasExpressionMarker)  ? _getExpressionCore(this, dp, metadata): (oldEntry.IsExpression)         ? (oldEntry.LocalValue as Expression):                                   null;// Allow expression to store value if new value is// not an Expression, if applicablebool handled = false;if ((currentExpr != null) && (newExpr == null)){// Resolve deferred references because we haven't modified// the expression code to work with DeferredReference yet.if (isDeferredReference){value = ((DeferredReference) value).GetValue(BaseValueSourceInternal.Local);}// CALLBACKhandled = currentExpr.SetValue(this, dp, value);entryIndex = CheckEntryIndex(entryIndex, dp.GlobalIndex);}// Create the new effective value entryEffectiveValueEntry newEntry;if (handled)                                                                                                                                                                                        ){// If expression handled set, then doneif (entryIndex.Found){newEntry = _effectiveValues[entryIndex.Index];}else{// the expression.SetValue resulted in this value being removed from the table;// use the default value.newEntry = EffectiveValueEntry.CreateDefaultValueEntry(dp, metadata.GetDefaultValue(this, dp));}coerceWithCurrentValue = false; // expression already handled the control-value}else{// allow a control-value to coerce an expression value, when the// expression didn't handle the valueif (coerceWithCurrentValue && currentExpr != null){currentExpr = null;}newEntry = new EffectiveValueEntry(dp, BaseValueSourceInternal.Local);// detach the old expression, if applicableif (currentExpr != null){// CALLBACKDependencySource[] currentSources = currentExpr.GetSources();UpdateSourceDependentLists(this, dp, currentSources, currentExpr, false);  // Remove// CALLBACKcurrentExpr.OnDetach(this, dp);currentExpr.MarkDetached();entryIndex = CheckEntryIndex(entryIndex, dp.GlobalIndex);}// attach the new expression, if applicableif (newExpr == null){// simple local value setnewEntry.HasExpressionMarker = newValueHasExpressionMarker;newEntry.Value = value;}else{Debug.Assert(!coerceWithCurrentValue, "Expression values not supported in SetCurrentValue");// First put the expression in the effectivevalueentry table for this object;// this allows the expression to update the value accordingly in OnAttachSetEffectiveValue(entryIndex, dp, dp.GlobalIndex, metadata, newExpr, BaseValueSourceInternal.Local);// Before the expression is attached it has default valueobject defaultValue = metadata.GetDefaultValue(this, dp);entryIndex = CheckEntryIndex(entryIndex, dp.GlobalIndex);SetExpressionValue(entryIndex, defaultValue, newExpr);UpdateSourceDependentLists(this, dp, newSources, newExpr, true);  // AddnewExpr.MarkAttached();// CALLBACKnewExpr.OnAttach(this, dp);// the attach may have added entries in the effective value table ...// so, update the entryIndex accordingly.entryIndex = CheckEntryIndex(entryIndex, dp.GlobalIndex);newEntry = EvaluateExpression(entryIndex,dp,newExpr,metadata,oldEntry,_effectiveValues[entryIndex.Index]);entryIndex = CheckEntryIndex(entryIndex, dp.GlobalIndex);}}UpdateEffectiveValue(entryIndex,dp,metadata,oldEntry,ref newEntry,coerceWithDeferredReference,coerceWithCurrentValue,operationType);}
ClearValueCommon
/// <summary>///     The common code shared by all variants of ClearValue/// </summary>private void ClearValueCommon(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata){if (IsSealed){throw new InvalidOperationException(SR.Get(SRID.ClearOnReadOnlyObjectNotAllowed, this));}// Get old valueEffectiveValueEntry oldEntry = GetValueEntry(entryIndex,dp,metadata,RequestFlags.RawEntry);// Get current local value// (No need to go through read local callback, just checking// for presence of Expression)object current = oldEntry.LocalValue;// Get current expressionExpression currentExpr = (oldEntry.IsExpression) ? (current as Expression) : null;// Inform value expression of detachment, if applicableif (currentExpr != null){// CALLBACKDependencySource[] currentSources = currentExpr.GetSources();UpdateSourceDependentLists(this, dp, currentSources, currentExpr, false);  // Remove// CALLBACKcurrentExpr.OnDetach(this, dp);currentExpr.MarkDetached();entryIndex = CheckEntryIndex(entryIndex, dp.GlobalIndex);}// valuesource == Local && value == UnsetValue indicates that we are clearing the local valueEffectiveValueEntry newEntry = new EffectiveValueEntry(dp, BaseValueSourceInternal.Local);// Property is now invalidUpdateEffectiveValue(entryIndex,dp,metadata,oldEntry,ref newEntry,false /* coerceWithDeferredReference */,false /* coerceWithCurrentValue */,OperationType.Unknown);}

Mode对SetValue的影响

wpf - 依赖属性SetValue()和SetCurrentValue()之间的区别是什么 - 堆栈溢出 — wpf - What’s the difference between Dependency Property SetValue() & SetCurrentValue() - Stack Overflow上看到和Mode还有关系,于是我又测试了一下:
在这里插入图片描述

将按钮的绑定方式改为OneWay,发现SetValue执行后,绑定为Null,绑定被销毁。

<Buttonx:Name="MyButton"Width="100"Height="50"Background="{Binding DefaultBackgroundColor,Mode=OneWay,RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"Content="MyButton" />

总结

就我的测试Demo来看,

  1. ClearValue会使得数据绑定失效。
  2. SetCurrentValue和官方文档所述一致,不会覆盖该属性的值来源,如果属性值是通过绑定、样式或触发器设置的,使用 SetCurrentValue 后,这些设置仍然有效。
  3. SetValue 设置的依赖属性为DependencyProperty.UnsetValue,会和ClearValue的表现一致。
  4. SetValue在Mode=TwoWay时,和SetCurrentValue表现一致,数据绑定不会失效。
  5. SetValue 在Mode=OneWay时。数据绑定也会失效。

参考

  1. What’s the difference between Dependency Property SetValue() & SetCurrentValue()
  2. Dependency property value precedence - WPF .NET | Microsoft Learn
  3. 源码

http://www.ppmy.cn/news/1537446.html

相关文章

通信工程学习:什么是AIOT智能物联网

AIOT&#xff1a;智能物联网 AIOT智能物联网&#xff0c;即Artificial Intelligence of Things&#xff08;人工智能物联网&#xff09;&#xff0c;是人工智能&#xff08;AI&#xff09;与物联网&#xff08;IoT&#xff09;技术的深度融合。这一技术通过物联网产生、收集来自…

【jvm】调整栈大小,能保证不会出现溢出吗

目录 1. 说明2. JVM栈溢出的原因3. 调整栈大小的作用与限制3.1 作用3.2 限制 4. 避免栈溢出的策略4.1 优化代码4.2 内存管理4.3 JVM参数调整4.4 监控与报警 1. 说明 1.在Java虚拟机&#xff08;JVM&#xff09;中&#xff0c;调整栈大小并不能保证完全避免栈溢出。 2. JVM栈溢…

达梦DBLINK访问ORACLE配置方法

目录 1、概述 2、测试环境 3、语法简介 4、配置访问DM的DBLINK 5、配置访问ORACLE的DBLINK 5.1 通过OCI配置 5.2 通过ODBC配置 1、概述 本文介绍了达梦DBLINK的配置方法。有3部分内容&#xff0c;1&#xff09;达梦访问到达梦的配置方法&#xff1b;2&#xff09;通过OC…

JavaScript 命令模式实战:打造可撤销的操作命令

一. 前言 在前端开发中&#xff0c;命令模式&#xff08;Command Pattern&#xff09;作为一种行为型设计模式&#xff0c;可以帮助我们将请求封装成一个对象&#xff0c;从而实现调用对象和执行对象之间的解耦&#xff0c;方便扩展和修改。 本文将和大家分享 JavaScript 中的…

视频怎么去除杂音保留人声?让人声更动听!视频噪音处理攻略

在视频制作过程中&#xff0c;音质是至关重要的一环。然而&#xff0c;很多时候我们录制的视频会伴随着各种不想要的杂音&#xff0c;比如风声、交通噪音或是其他环境音&#xff0c;这些杂音严重影响了观众的观看体验。那么&#xff0c;如何在保留人声的同时&#xff0c;有效地…

python爬虫题目

网站 https://project-iprj6705f17ebcfad66461658c5c-8000.preview.node01.inscode.run/ 第一道题爬取api并且保存 import requests,re import json url "https://project-iprj6705f17ebcfad66461658c5c-8000.preview.node01.inscode.run/tasks/api/" headers {us…

Linux相关概念和易错知识点(15)(exec系列函数)

进程程序替换 我们之前已经知道如何创建子进程了。fork函数内部会为我们拷&#xff0c;数据和代码&#xff0c;创建PCB&#xff0c;当走到最后return语句时两个进程分别返回&#xff0c;之后父子进程保持独立。虽然我们可以控制代码逻辑使得父子进程走的代码不同&#xff0c;父…

洗衣店数字化转型:Spring Boot订单管理

3系统分析 3.1可行性分析 通过对本洗衣店订单管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本洗衣店订单管理系统采用JAVA作为开发语言&#xff0c;S…