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

news2024/10/9 6:23:52

引言

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

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

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

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

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

其主要作用如下:

1. myButton绑定默认背景颜色的依赖属性
<Button
    x: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时才会是清除本地值,并且 ClearValue
            IsBindingExpressionNull();
        }
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时为true
            OperationType       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 Clear
            if( 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 scenarios
            if (!newValueHasExpressionMarker)
            {
                bool isValidValue = isInternal ? dp.IsValidValueInternal(value) : dp.IsValidValue(value);
 
                // for properties of type "object", we have to always check for expression & deferredreference
                if (!isValidValue || dp.IsObjectType)
                {
                    // 2nd most common is expression
                    newExpr = 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
                        // CALLBACK
                        newSources = newExpr.GetSources();
                        ValidateSources(this, newSources, newExpr);
                    }
                    else
                    {
                        // and least common is DeferredReference
                        isDeferredReference = (value is DeferredReference);
                        if (!isDeferredReference)
                        {
                            if (!isValidValue)
                            {
                                // it's not a valid value & it's not an expression, so throw
                                throw new ArgumentException(SR.Get(SRID.InvalidPropertyValue, value, dp.Name));
                            }
                        }
                    }
                }
            }
 
            // Get old value
            EffectiveValueEntry 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 now
            Expression 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 applicable
 
            bool 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);
                }
 
                // CALLBACK
                handled = currentExpr.SetValue(this, dp, value);
                entryIndex = CheckEntryIndex(entryIndex, dp.GlobalIndex);
            }
 
            // Create the new effective value entry
            EffectiveValueEntry newEntry;
             if (handled)                                                                                                                                                                                        )
            {
                // If expression handled set, then done
                if (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 value
                if (coerceWithCurrentValue && currentExpr != null)
                {
                    currentExpr = null;
                }
 
                newEntry = new EffectiveValueEntry(dp, BaseValueSourceInternal.Local);
 
                // detach the old expression, if applicable
                if (currentExpr != null)
                {
                    // CALLBACK
                    DependencySource[] currentSources = currentExpr.GetSources();
 
                    UpdateSourceDependentLists(this, dp, currentSources, currentExpr, false);  // Remove
 
                    // CALLBACK
                    currentExpr.OnDetach(this, dp);
                    currentExpr.MarkDetached();
                    entryIndex = CheckEntryIndex(entryIndex, dp.GlobalIndex);
                }
 
                // attach the new expression, if applicable
                if (newExpr == null)
                {
                    // simple local value set
                    newEntry.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 OnAttach
                    SetEffectiveValue(entryIndex, dp, dp.GlobalIndex, metadata, newExpr, BaseValueSourceInternal.Local);
 
                    // Before the expression is attached it has default value
                    object defaultValue = metadata.GetDefaultValue(this, dp);
                    entryIndex = CheckEntryIndex(entryIndex, dp.GlobalIndex);
                    SetExpressionValue(entryIndex, defaultValue, newExpr);
                    UpdateSourceDependentLists(this, dp, newSources, newExpr, true);  // Add
 
                    newExpr.MarkAttached();
 
                    // CALLBACK
                    newExpr.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 value
            EffectiveValueEntry 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 expression
            Expression currentExpr = (oldEntry.IsExpression) ? (current as Expression) : null;
 
            // Inform value expression of detachment, if applicable
            if (currentExpr != null)
            {
                // CALLBACK
                DependencySource[] currentSources = currentExpr.GetSources();
 
                UpdateSourceDependentLists(this, dp, currentSources, currentExpr, false);  // Remove
 
                // CALLBACK
                currentExpr.OnDetach(this, dp);
                currentExpr.MarkDetached();
                entryIndex = CheckEntryIndex(entryIndex, dp.GlobalIndex);
            }
 
            // valuesource == Local && value == UnsetValue indicates that we are clearing the local value
            EffectiveValueEntry newEntry = new EffectiveValueEntry(dp, BaseValueSourceInternal.Local);
 
            // Property is now invalid
            UpdateEffectiveValue(
                    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,绑定被销毁。

<Button
    x: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.coloradmin.cn/o/2194780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣 中等 216组合总和III

文章目录 题目介绍解法 题目介绍 解法 是77.组合链接的扩展 class Solution {List<List<Integer>> result new ArrayList<>();List<Integer> path new ArrayList<>();public List<List<Integer>> combinationSum3(int n, int k) …

流速仪设备操作说明

1 流速仪设备安装 按图示对流速仪设备进行安装&#xff0c;主要是流速仪和电频。 连接电脑&#xff0c;直接插上信号接收器&#xff0c;后面使用蓝牙连接。 2 安装软件 3 双击ParaniWin进行设备连接 3.1 按图片所示&#xff0c;设置端口等信息 3.2 Device Setting —>输…

2024软件设计师高频考点体系—软件工程体系考点大全

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 更多高频考点&#x1f9e7;&#x1f7e5;软件设计师高频考点电子手册✨点击进入&#x1f381;&#x1f7e6; 软件设计师高频考点…

力扣59.螺旋矩阵||

题目链接&#xff1a;59. 螺旋矩阵 II - 力扣&#xff08;LeetCode&#xff09; 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff…

【C/C++】错题记录(五)

题目一 题目二 在 16 位机器上&#xff0c;通常以 2 字节为边界对齐。 首先看 char a&#xff0c;它占用 1 个字节。接着是 int b&#xff0c;占用 2 个字节。由于要满足边界对齐&#xff0c;在 char a后面会填充 1 个字节&#xff0c;使得 int b从 2 字节边界开始存储。最后是…

TMGM开户后还可以开代理账户吗

在TMGM平台进行交易的人也想开立代理账户。那么&#xff0c;TMGM的代理账户如何开设&#xff1f;答案是&#xff1a;首先需要先开设个人交易账户&#xff0c;然后再申请成为代理商。 1. 先开个人交易账户 在TMGM开设代理账户的首要步骤是先拥有一个个人交易账户。您可以通过访…

Carsim报错总结及解决方法

1. simulink报错&#xff1a;vs_state 、StopMode无法识别 - matlab命令行窗口输入&#xff1a;vs_state -1&#xff0c;StopMode -1 2. Video变暗&#xff0c;无法点击 - 说明书中提示&#xff1a;如果输出文件不存在&#xff08;例如&#xff0c;在单击复制按钮创…

身份证二要素-身份证二要素接口-身份证尔雅欧批量核验

身份证批量核查简介&#xff1a;【身份证批量核查】极速查询、多线程、高并发&#xff0c;可批量上传excel文件&#xff0c;2万条数据约30分钟核验完成。无需技术参与、自主便捷查询 1、登录后&#xff0c;点击右上角【个人中心】按钮&#xff0c;进入个人中心页面 2、进入个…

基于单片机的信号选择与温度变化

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采用DS18B20检测温度&#xff0c;通过三种LED灯代表不同状态。 采用DAC0832显示信号脉冲&#xff0c;通过8位数码管显示温度。 信号脉冲可以根据两个按键分别调整为正弦…

5G NR 切换流程简介

文章目录 切换类型分类切换步骤测量事件分类5G NR系统内切换信令流程 切换类型分类 5G NR 的切换类型分为 系统间切换和系统内切换&#xff0c;而 系统间切换又分为 5G NR 与 4G LTE 和5G NR 与3G WCDMA的切换。站内切换则分为站内切换和站间间切换。 切换步骤 主要分为三个阶…

边缘端大模型是怎么部署的?重点关注哪些?

写在前面 在设备端运行的大语言模型&#xff08;LLMs&#xff09;&#xff0c;即指在边缘设备上运行LLMs&#xff0c;因其出色的隐私保护、低延迟和节省带宽的特点而引起了广泛关注。然而&#xff0c;与功能更为强大的云中心相比&#xff0c;设备端LLMs的能力本质上受到边缘设…

高效数据处理:MapReduce与Hive的实战应用

文章目录 hive分析汇总互联网日志分析1.项目需求2.数据说明3.算法思路 用户电影推荐1.项目需求2.数据说明3.算法思路4.解题步骤 简单数据统计WordCount数据说明 疫情数据分析1.项目需求2.数据说明step1:创建ods层数据表step2&#xff1a;创建dwd层数据表step3&#xff1a;创建d…

Vue84 vue3项目结构分析

打开main.js文件&#xff0c;发现和vue2不同 //引入的不再是Vue构造函数了&#xff0c;引入的是一个名为createApp的工厂函数 import { createApp } from vue import App from ./App.vue//创建应用实例对象——app(类似于之前Vue2中的vm&#xff0c;但app比vm更“轻”) const …

D29【python 接口自动化学习】- python基础之输入输出与文件操作

day29 格式化输出 学习日期&#xff1a;20241006 学习目标&#xff1a;输入输出与文件操作&#xfe63;-41 格式化输出&#xff1a;如何将执行结果通过屏幕输出&#xff1f; 学习笔记&#xff1a; 三种常用的格式化输出方式 百分号方式 format函数方式 总结 1. 格式化输出…

win10服务器启动且未登录时自动启动程序

场景&#xff1a;公司服务器安装了几个程序&#xff0c;当服务器断电重启之后希望程序能自动打开&#xff0c;而不需要手动登录服务器打开。 因为软件是自己开发的所以安全方面这里没有考虑。 1.打开服务器管理器&#xff0c;点击工具&#xff0c;选择任务计划程序 2.在任务计…

文件处理不再难:带你轻松攻克C语言文件操作

嘿嘿,家人们,今天咱们来详细剖析C语言中的文件操作,好啦,废话不多讲,开干! 目录 1:为什么使用文件 2:文件的概念 2.1:程序文件 2.2:数据文件 2.3:文件名 3:二进制文件与文本文件 4:文件的打开与关闭 4.1:流与标准流 4.1.1:流 4.1.2:标准流 4.2:文件指针 4.3:文件的…

小米路由器ax1500+DDNS+公网IP+花生壳实现远程访问

有远程办公的需求&#xff0c;以及一些其他东西。 为什么写&#xff1f; ax1500路由器好像没搜到相关信息。以及其中有一点坑。 前置 公网ip Xiaomi路由器 AX1500 MiWiFi 稳定版 1.0.54 实现流程 花生壳申请壳域名https://console.hsk.oray.com/ 这里需要为域名实名认证 …

深度学习-----------------------注意力分数

目录 注意力分数注意力打分函数代码 掩蔽softmax操作拓展到高纬度Additive Attention(加性注意力)加性注意力代码演示一下AdditiveAttention类该部分总代码注意力权重 Scaled Dot-Product Attention&#xff08;缩放点积注意力&#xff09;缩放点积注意力代码演示一下DotProduc…

35-搜索插入位置

题目:35. 搜索插入位置 - 力扣&#xff08;LeetCode&#xff09; 思想:常规的二分&#xff0c;很简单的理解是当大于所有数时要在right的边界1插入 class Solution { public:int searchInsert(vector<int>& nums, int target) {int left 0;int right nums.size()-…

“高效解决PL/SQL Developer软件过期问题的方法“

目录 背景&#xff1a; 解决方法&#xff1a; 方法1&#xff1a;删除注册表信息 ​方法2&#xff1a;使用注册码 那种方式更好&#xff1a; 背景&#xff1a; 前段时间&#xff0c;由于项目需要&#xff0c;我下载了PL/SQL Developer这款强大集成开发环境&#xff0c;(ID…