WPF 的 ElementName 在 ContextMenu 中无法绑定成功?试试使用 x:Reference!
在 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>
注意,MenuItem
的 Header
属性设置为和 Run
的 Text
属性一模一样的绑定字符串。不过运行之后的截图显示,右键菜单中并没有如预期般出现绑定的字符串。
使用 x:Reference 代替 ElementName 能够解决
以上绑定失败的原因,是 Grid.ContextMenu
属性中赋值的 ContextMenu
不在可视化树中,而 ContextMenu
又不是一个默认建立 ScopeName 的控件,此时既没有自己指定 NameScope,有没有通过可视化树寻找上层设置的 NameScope,所以在绑定上下文中是找不到 WalterlvWindow
的。如果调用去查找,得到的是 null
。详见:WPF 中的 NameScope。
类似的情况也发生在设置非可视化树或逻辑树的属性时,典型的比如在 Grid.Row
或 Grid.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’.
因为给 MenuItem
的 Header
属性绑定赋值的时候,创建绑定表达式用到了 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!的更多相关文章
- WPF 只读集合在 XAML 中的绑定(WPF:Binding for readonly collection in xaml)
问题背景 某一天,我想做一个签到打卡的日历.基于 Calendar,想实现这个目标,于是找到了它的 SelectedDates 属性,用于标记签到过的日期. 问题来了. 基于MVVM模式,想将其在xa ...
- WPF Binding ElementName方式无效的解决方法--x:Reference绑定
原文:WPF Binding ElementName方式无效的解决方法--x:Reference绑定 需求: 背景:Grid的有一个TextBlock name:T1和一个ListBox,ListBo ...
- WPF 让普通 CLR 属性支持 XAML 绑定(非依赖属性),这样 MarkupExtension 中定义的属性也能使用绑定了
原文:WPF 让普通 CLR 属性支持 XAML 绑定(非依赖属性),这样 MarkupExtension 中定义的属性也能使用绑定了 版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4 ...
- 封装:WPF中可以绑定的BindPassWord控件
原文:封装:WPF中可以绑定的BindPassWord控件 一.目的:本身自带的PassWord不支持绑定 二.Xaml部分 <UserControl x:Class="HeBianG ...
- WPF触发器(非数据库中的触发器)
一.什么是触发器?触发器(Trigger)就是当某种条件满足后即完成相应逻辑功能的一部分程序组成.在当前的WPF中,Trigger一共有三种类型,它们分别是: (1)属性触发器:其对应的类是Trigg ...
- WPF使用MVVM(一)-属性绑定
WPF使用MVVM(一)-属性绑定 简单介绍MVVM MVVM是Model(数据类型),View(界面),ViewModel(数据与界面之间的桥梁)的缩写,是一种编程模式,优点一劳永逸,初步增加一些逻 ...
- WPF使用MVVM(二)-命令绑定
WPF使用MVVM(二)-命令绑定 上一节已经介绍了WPF的属性绑定,这使得我们只需要指定界面的DataContext,然后就可以让界面绑定我们的属性数据呢. 但是上一节还遗留了一个问题就是我们的按钮 ...
- Winfrom中ListBox绑定List数据源更新问题
Winfrom中ListBox绑定List数据源更新问题 摘自:http://xiaocai.info/2010/09/winform-listbox-datasource-update/ Winfr ...
- JavaScript中事件绑定的方法总结
最近收集了一些关于JavaScript绑定事件的方法,汇总了一下,不全面,但是,希望便于以后自己查看. JavaScript中绑定事件的方法主要有三种: 1 在DOM元素中直接绑定 2 JavaScr ...
随机推荐
- Redis Python开发指南
redis基本命令 String set setex psetex mset mget getset getrange setrange setbit getbi ...
- win7 +v Ubuntu 16.04 grub rescue 模式下修复 grub
前几天整理了下电脑的分区,合并并删除一些分区,结果导致 grub 被破坏了,Ubuntu进不去了,启动后直接进入了 rescure 模式.后来又折腾了下,终于修复好了,现总结一下. 先说一下我的系统环 ...
- VS2010/MFC编程入门之二十六(常用控件:滚动条控件Scroll Bar)
回顾上一节,鸡啄米讲的是组合框控件Combo Box的使用.本节详解滚动条控件Scroll Bar的相关内容. 滚动条控件简介 滚动条大家也很熟悉了,Windows窗口中很多都有滚动条.前面讲的列表框 ...
- zw版【转发·台湾nvp系列Delphi例程】HALCON MoveRectangle
zw版[转发·台湾nvp系列Delphi例程]HALCON MoveRectangle procedure TForm1.Button1Click(Sender: TObject);var img : ...
- json反序列化快捷实体类
有时候我们反序列化一个json串只为了提取里面的数据,而且json串的层级结构可能会比较复杂,定义对应的实体类会多而杂,有时还不一定能达到想要的效果. 则可以关注下以下两个类: java : ...
- Ubuntu16.04下安装tensorflow(GPU加速)【转】
本文转载自:https://blog.csdn.net/qq_30520759/article/details/78947034 版权声明:本文为博主原创文章,未经博主允许不得转载. https:// ...
- Python学习札记(二十三) 函数式编程4 sorted
参考:sorted NOTE 1.sorted,快速排序,时间复杂度O(nlogn)渐进最优. #!/usr/bin/env python3 L = [] for i in range(10): L. ...
- POJ 1047 Round and Round We Go
https://vjudge.net/problem/POJ-1047 题意: 给一个整数,它的长度为n,从1开始一直到n和该整数相乘,判断每次结果是否和原来的整数是循环的. 思路: 大整数的乘法. ...
- 插入10W数据的两个程序比较
程序1 添加10W数据 $count = 0; for ($i = 1;$i <= 100000 ;$i++) { $add_data = [ 'id' => $i, 'username' ...
- vim 将文件从dos格式转换到unix格式
dos格式文件传输到unix系统时,会在每行的结尾多一个^M(/r),当然也有可能看不到.但是在vim的时候,会在下面显示此文件的格式,比如 "dos.txt" [dos] 120 ...