在 Binding 中使用 ElementName 司空见惯,没见它出过什么事儿。不过当你预见 ContextMenu,或者类似 Grid.Row / Grid.Column 这样的属性中设置的时候,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}";
}

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


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


参考资料

WPF 的 ElementName 在 ContextMenu 中无法绑定成功?试试使用 x:Reference!的更多相关文章

  1. WPF 只读集合在 XAML 中的绑定(WPF:Binding for readonly collection in xaml)

    问题背景 某一天,我想做一个签到打卡的日历.基于 Calendar,想实现这个目标,于是找到了它的 SelectedDates 属性,用于标记签到过的日期. 问题来了. 基于MVVM模式,想将其在xa ...

  2. WPF Binding ElementName方式无效的解决方法--x:Reference绑定

    原文:WPF Binding ElementName方式无效的解决方法--x:Reference绑定 需求: 背景:Grid的有一个TextBlock name:T1和一个ListBox,ListBo ...

  3. WPF 让普通 CLR 属性支持 XAML 绑定(非依赖属性),这样 MarkupExtension 中定义的属性也能使用绑定了

    原文:WPF 让普通 CLR 属性支持 XAML 绑定(非依赖属性),这样 MarkupExtension 中定义的属性也能使用绑定了 版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4 ...

  4. 封装:WPF中可以绑定的BindPassWord控件

    原文:封装:WPF中可以绑定的BindPassWord控件 一.目的:本身自带的PassWord不支持绑定 二.Xaml部分 <UserControl x:Class="HeBianG ...

  5. WPF触发器(非数据库中的触发器)

    一.什么是触发器?触发器(Trigger)就是当某种条件满足后即完成相应逻辑功能的一部分程序组成.在当前的WPF中,Trigger一共有三种类型,它们分别是: (1)属性触发器:其对应的类是Trigg ...

  6. WPF使用MVVM(一)-属性绑定

    WPF使用MVVM(一)-属性绑定 简单介绍MVVM MVVM是Model(数据类型),View(界面),ViewModel(数据与界面之间的桥梁)的缩写,是一种编程模式,优点一劳永逸,初步增加一些逻 ...

  7. WPF使用MVVM(二)-命令绑定

    WPF使用MVVM(二)-命令绑定 上一节已经介绍了WPF的属性绑定,这使得我们只需要指定界面的DataContext,然后就可以让界面绑定我们的属性数据呢. 但是上一节还遗留了一个问题就是我们的按钮 ...

  8. Winfrom中ListBox绑定List数据源更新问题

    Winfrom中ListBox绑定List数据源更新问题 摘自:http://xiaocai.info/2010/09/winform-listbox-datasource-update/ Winfr ...

  9. JavaScript中事件绑定的方法总结

    最近收集了一些关于JavaScript绑定事件的方法,汇总了一下,不全面,但是,希望便于以后自己查看. JavaScript中绑定事件的方法主要有三种: 1 在DOM元素中直接绑定 2 JavaScr ...

随机推荐

  1. docker——三剑客之Docker swarm

    Docker Swarm是Docker官方的三剑客项目之一,提供Docker容器集群服务,是Docker官方对容器云生态进行支持的核心方案.使用它,用户可以将多个Docker主机封装为单个大型的虚拟D ...

  2. cocos代码研究(21)Widget子类Text,TextAtlas,TextBMFont学习笔记

    理论基础 Text类又称ttf格式文本,可以用ttf文件或者系统自带字体,支持文字多,但是ttf文件格式体积大,渲染速度慢: TextBMFont类又称fnt格式文本,纹理创建,根据纹理上有的文字来显 ...

  3. CCPC-Wannafly Winter Camp Day3 (Div2, onsite)

    Replay Dup4: 没想清楚就动手写? 写了两百行发现没用?想的还是不够仔细啊. 要有莽一莽的精神 X: 感觉今天没啥输出啊, 就推了个公式?抄了个板子, 然后就一直自闭A. 语文差,题目没理解 ...

  4. Python: 分数运算

    fractions 模块可以被用来执行包含分数的数学运算 >>> from fractions import Fraction >>> a = Fraction(5 ...

  5. i春秋之荒岛求生write-up

    i春秋之荒岛求生write-up 第一关 这一关的答案是在题目的最后一句加粗的 躺平等死 和 勇敢战斗 中进行选择,结合前文中提到的 如果你想出去,就必须打败他们 自然得出答案是 勇敢战斗 . 第二关 ...

  6. 辅助模块应用(auxiliary/scanner/portscan/tcp)

    实验步骤 创建msf所需的数据库 之前我们开启msf时下面总会出现一个红色的小减号,原来是因为没有和数据库键连接,于是首先我们要手动建立一个数据库... 使用命令来实现: service postgr ...

  7. 20145326 《Java程序设计》实验五——Java网络编程及安全实验报告

    实验内容 1.掌握Socket程序的编写: 2.掌握密码技术的使用: 3.设计安全传输系统. 实验步骤 我和20145211黄志远http://www.cnblogs.com/nostalgia-/组 ...

  8. NO.1 在Eclipse中安装Maven插件安装详解

    前言 本来是没打算写博客的,作为一个13年毕业的菜鸟,自认为水平太渣写不出什么好文章,但是前些日子看到一篇鼓励性质的文章说,技术人员的成长靠的就是点点滴滴的积累,博客内容不一定包含多么高深的内容,但是 ...

  9. Shell脚本之无限循环的两种方法

    for #!/bin/bash ;i<;)) do let "j=j+1" echo "-------------j is $j ----------------- ...

  10. java set初始化问题

    set在执行add方法时,多次报空指针异常,后来发现Set初始化时,如果是 Set<Type> set = null; 这样的话,在执行 set.add(element)的时候会报空指针异 ...