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>注意,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}"; }現在運行,右鍵菜單已經正常完成了綁定。
▲ 右鍵菜單已經正常完成了綁定
參考資料
- c# - WPF databinding error in Tag property - Stack Overflow
總結
以上是生活随笔為你收集整理的WPF 的 ElementName 在 ContextMenu 中无法绑定成功?试试使用 x:Reference!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kotlin中的高阶函数
- 下一篇: 12 Django cooking与se