原文:WPF 的 ElementName 在 ContextMenu 中无法绑定成功?试试使用 x:Reference!

版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:http://blog.csdn.net/wpwalter/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系(walter.lv@qq.com)。 https://blog.csdn.net/WPwalter/article/details/83473313

在 Binding 中使用 ElementName 司空见惯,没见它出过什么事儿。不过当你预见 ContextMenu,或者类似 Grid.Row / Grid.Column 这样的属性中设置的时候,ElementName 就不那么管用了。

本文将解决这个问题。


本文内容

  • 以下代码是可以正常工作的
  • 以下代码就无法正常工作了
  • 使用 x:Reference 代替 ElementName 能够解决
    • 参考资料

以下代码是可以正常工作的

<Window x:Class="Walterlv.Demo.BindingContext.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"x:Name="WalterlvWindow" Title="Walterlv Binding Demo" Height="450" Width="800"><Grid Background="LightGray" Margin="1 1 1 0" MinHeight="40"><TextBlock><Run Text="{Binding Mode=OneWay}" FontSize="20" /><LineBreak /><Run Text="{Binding ElementName=WalterlvWindow, Path=DemoText, Mode=OneWay}" /></TextBlock></Grid>
</Window>

在代码中,我们为一段文字中的一个部分绑定了主窗口的的一个属性,于是我们使用 ElementName 来指定绑定源为 WalterlvWindow


▲ 使用普通的 ElementName 绑定

以下代码就无法正常工作了

保持以上代码不变,我们现在新增一个 ContextMenu,然后在 ContextMenu 中使用一模一样的绑定表达式:

<Window x:Class="Walterlv.Demo.BindingContext.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"x:Name="WalterlvWindow" Title="Walterlv Binding Demo" Height="450" Width="800"><Grid Background="LightGray" Margin="1 1 1 0" MinHeight="40"><Grid.ContextMenu><ContextMenu><MenuItem Header="{Binding ElementName=WalterlvWindow, Path=DemoText, Mode=OneWay}" /></ContextMenu></Grid.ContextMenu><TextBlock><Run Text="{Binding Mode=OneWay}" FontSize="20" /><LineBreak /><Run Text="{Binding ElementName=WalterlvWindow, Path=DemoText, Mode=OneWay}" /></TextBlock></Grid>
</Window>

注意,MenuItemHeader 属性设置为和 RunText 属性一模一样的绑定字符串。不过运行之后的截图显示,右键菜单中并没有如预期般出现绑定的字符串。

使用 x:Reference 代替 ElementName 能够解决

以上绑定失败的原因,是 Grid.ContextMenu 属性中赋值的 ContextMenu 不在可视化树中,而 ContextMenu 又不是一个默认建立 ScopeName 的控件,此时既没有自己指定 NameScope,有没有通过可视化树寻找上层设置的 NameScope,所以在绑定上下文中是找不到 WalterlvWindow 的。如果调用去查找,得到的是 null。详见:WPF 中的 NameScope。

类似的情况也发生在设置非可视化树或逻辑树的属性时,典型的比如在 Grid.RowGrid.Column 属性上绑定时,ElementName 也是失效的。

此时最适合的情况是直接使用 x:Reference

  <Window x:Class="Walterlv.Demo.BindingContext.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"x:Name="WalterlvWindow" Title="Walterlv Binding Demo" Height="450" Width="800"><Grid Background="LightGray" Margin="1 1 1 0" MinHeight="40"><Grid.ContextMenu><ContextMenu>
-                 <MenuItem Header="{Binding ElementName=WalterlvWindow, Path=DemoText, Mode=OneWay}" />
+                 <MenuItem Header="{Binding Source={x:Reference WalterlvWindow}, Path=DemoText, Mode=OneWay}" /></ContextMenu></Grid.ContextMenu><TextBlock><Run Text="{Binding Mode=OneWay}" FontSize="20" /><LineBreak /><Run Text="{Binding ElementName=WalterlvWindow, Path=DemoText, Mode=OneWay}" /></TextBlock></Grid></Window>

不过,这是个假象,因为此代码运行时会抛出异常:

XamlObjectWriterException: Cannot call MarkupExtension.ProvideValue because of a cyclical dependency. Properties inside a MarkupExtension cannot reference objects that reference the result of the MarkupExtension. The affected MarkupExtensions are:
‘System.Windows.Data.Binding’ Line number ‘8’ and line position ‘27’.

因为给 MenuItemHeader 属性绑定赋值的时候,创建绑定表达式用到了 WalterlvWindow,但此时 WalterlvWindow 尚在构建(因为里面的 ContextMenu 是窗口的一部分),于是出现了循环依赖。而这是不允许的。

为了解决循环依赖问题,我们可以考虑将 x:Reference 放到资源中。因为资源是按需创建的,所以这不会造成循环依赖。

那么总得有一个对象来承载我们的绑定源。拿控件的 Tag 属性也许是一个方案,不过专门为此建立一个绑定代理类也许是一个更符合语义的方法:

  <Window x:Class="Walterlv.Demo.BindingContext.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+         xmlns:local="clr-namespace:Walterlv.Demo.BindingContext"x:Name="WalterlvWindow" Title="Walterlv Binding Demo" Height="450" Width="800">
+     <Window.Resources>
+         <local:BindingProxy x:Key="WalterlvBindingProxy" Data="{x:Reference WalterlvWindow}" />
+     </Window.Resources><Grid Background="LightGray" Margin="1 1 1 0" MinHeight="40"><Grid.ContextMenu><ContextMenu>
-                 <MenuItem Header="{Binding Source={x:Reference WalterlvWindow}, Path=DemoText, Mode=OneWay}" />
+                 <MenuItem Header="{Binding Source={StaticResource WalterlvBindingProxy}, Path=Data.DemoText, Mode=OneWay}" /></ContextMenu></Grid.ContextMenu><TextBlock><Run Text="{Binding Mode=OneWay}" FontSize="20" /><LineBreak /><Run Text="{Binding ElementName=WalterlvWindow, Path=DemoText, Mode=OneWay}" /></TextBlock></Grid></Window>

至于 BindingProxy,非常简单:

public sealed class BindingProxy : Freezable
{public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new PropertyMetadata(default(object)));public object Data{get => (object) GetValue(DataProperty);set => SetValue(DataProperty, value);}protected override Freezable CreateInstanceCore() => new BindingProxy();public override string ToString() => Data is FrameworkElement fe? $"BindingProxy: {fe.Name}": $"Binding Proxy: {Data?.GetType().FullName}";
}

现在运行,右键菜单已经正常完成了绑定。


▲ 右键菜单已经正常完成了绑定


参考资料

  • c# - WPF databinding error in Tag property - Stack Overflow

WPF 的 ElementName 在 ContextMenu 中无法绑定成功?试试使用 x:Reference!相关推荐

  1. 【WPF】右键菜单ContextMenu可点击区域太小的问题

    [WPF]右键菜单ContextMenu可点击区域太小的问题 原文:[WPF]右键菜单ContextMenu可点击区域太小的问题 问题描述 正常使用右键菜单ContextMenu时,如果菜单项是不变的 ...

  2. 基于继承类的属性模版中无法绑定的原因和解决方法

    原文:基于继承类的属性模版中无法绑定的原因和解决方法 这个的晚上想学学WPF 做一个类似于ERP 左边菜单.构思一下思路 ...... 创建一个类基于Expander类. 1 public class ...

  3. SpreadJS 在 Angular2 中支持绑定哪些属性?

    SpreadJS 纯前端表格控件是基于 HTML5 的 JavaScript 电子表格和网格功能控件,提供了完备的公式引擎.排序.过滤.输入控件.数据可视化.Excel 导入/导出等功能,适用于 .N ...

  4. 【WPF】拖拽ListBox中的Item

    原文:[WPF]拖拽ListBox中的Item 整理了两个关于WPF拖拽ListBox中的Item的功能.项目地址 https://github.com/Guxin233/WPF-DragItemIn ...

  5. 在WPF的WebBrowser控件中抑制脚本错误

    在WPF的WebBrowser控件中抑制脚本错误 原文:在WPF的WebBrowser控件中抑制脚本错误 今天用WPF的WebBrowser控件的时候,发现其竟然没有ScriptErrorsSuppr ...

  6. react回调函数_React中的回调中自动绑定ES6类函数

    在使用ES6类的React组件时,您必须遇到这种现象,必须显式绑定类函数,然后将其传递给诸如onClick.例如,采用以下示例. import React from 'react';class MyC ...

  7. MVVM 下 ContextMenu的命令绑定

    原文:MVVM 下 ContextMenu的命令绑定 由于ContextMenu不继承父级的DataContext,所以如果要绑定父级的DataContext,直接DataContext=" ...

  8. vue中class绑定函数

    vue中class绑定函数 vue+class类应用函数,增加class类名 相关博客: v-for循环.v-if 动态判断+动态赋值+操作class类:(计算属性)强制绑定函数 以上就是关于&quo ...

  9. VFP中轻松绑定 Windows 事件

    轻松绑定 Windows 事件 作者:Doug Hennig 译者:fbilo VFP 所缺少的在其它开发环境中的一个功能是捕捉 Windows 事件的能力.VFP 9 扩展了 BindEvent() ...

最新文章

  1. 深度分析typedef--定义自己的数据类型
  2. defaultdict python_python中defaultdict的用法详解
  3. 极速理解设计模式系列:9.工厂方法模式(Factory Method Pattern)
  4. 数塔 HDU - 2084
  5. linux系统预定义变量有哪些,C++中几个预定义变量的介绍
  6. python yield: send, close, throw
  7. 基于多视角学习和个性化注意力机制的新闻推荐(附论文下载链接)
  8. matlab连接散射点,使用小波散射做信号分类
  9. 【Hive】实战之电商平台销售数据探索分析案例(含数据)
  10. 联想小新打印机M7268W配置步骤
  11. 百度云满速下载原理与方法
  12. Java实现12306登录和查票
  13. NewLand手持设备上条码扫描
  14. 王森:程序设计师真情忏悔录
  15. 英语不好学不好编程?程序员记忆单词专属诀窍,效果简直要逆天
  16. wps演示文稿训练心得---实用版
  17. 文件系统的类型是RAW,CHKDSK无法供RAW驱动器使用
  18. 【溯源篇】CDN是什么
  19. 录音、上传、播放音频微信小程序实践
  20. 互联网校招求职经历记录

热门文章

  1. java.lang.OutOfMemoryError---at java.lang.StringBuilder.append
  2. Android 动画(二)
  3. Android调用手机浏览器打开某网页出现异常情况
  4. c++学习笔记之继承和多态
  5. 操作系统修炼秘籍(1):秘籍简介
  6. SDNU 1263.C语言程序设计教程(第三版)课后习题10.5(约瑟夫环)
  7. Cnblogs美化总结
  8. 【LeetCode】36. Valid Sudoku
  9. 【译】Using Objects to Organize Your Code
  10. Linux0.11内核--加载可执行二进制文件之1.copy_strings