[WPF] 绑定元素不在可视树或逻辑树上时如何绑定(Cannot find governing FrameworkElement or FrameworkContentElement)

原文地址

WPF中的DataContext属性非常方便,但在某些情况下,DataContext是不可访问的,比如,当你想绑定的元素不属于其逻辑树或可视树时,想正常使用绑定就可能非常困难……

让我们给一个简单的例子予以说明:我们要在DataGrid中显示产品列表。 在其中,我们希望能够基于ViewModel中公开的ShowPrice属性的值来显示或隐藏Price列。 一种明显的方法是将列的Visibility绑定到ShowPrice属性:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding ShowPrice,
                        Converter={StaticResource visibilityConverter}}"/>

但是,我们很快就会发现这样并不起作用,Price列会一直可见。为什么?如果我们看一下Visual Studio的Output窗口,会发现以下几行提示:

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ShowPrice; DataItem=null; target element is ‘DataGridTextColumn’ (HashCode=32685253); target property is ‘Visibility’ (type ‘Visibility’)

提示信息比较晦涩,但其实意思很简单:WPF不知道使用哪个FrameworkElement来获取DataContext,因为该列不属于DataGrid的逻辑树或可视树。

我们还可以尝试更改绑定的方式,比如这样:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding DataContext.ShowPrice,
                        Converter={StaticResource visibilityConverter},
                        RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>

或者在ShowPrice中添加一个CheckBox,尝试将Visibility绑定到它的IsChecked属性:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding IsChecked,
                        Converter={StaticResource visibilityConverter},
                        ElementName=chkShowPrice}"/>

但是这些都不奏效,我们还是会得到相同的结果。

这时,我们唯一的方法似乎只剩下在后台代码中修改它的Visibility,而这恰恰是我们在MVVM模式中所尽量避免的。

解决问题的方法其实很简单,就是充分利用Freezable类的优势。Freezable原本目的是定义具有可修改状态和只读状态的object,但有趣的是,Freezable可以继承那些甚至不在逻辑树或可视树中的DataContext。我目前还不清楚其背后的运行机理,但是我们可以利用这个优势完成我们的绑定工作。

方法是创建一个我称之为BindingProxy的继承了Freezable的类,以及定义了一个名为Data的依赖项属性:

public class BindingProxy : Freezable
    {
        #region Overrides of Freezable

        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }

        #endregion

        public object Data
        {
            get { return (object)GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy));
    }

然后,我们可以在DataGrid的资源中声明此类的实例,并将Data属性绑定到当前的DataContext

<DataGrid.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

最后一步是指定此BindingProxy对象作为绑定的源:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding Data.ShowPrice,
                        Converter={StaticResource visibilityConverter},
                        Source={StaticResource proxy}}"/>

需要注意,绑定路径的前缀应为“ Data”,因为该路径现在是相对于BindingProxy对象的。完成之后,绑定就可以生效,根据ShowPrice属性正确显示或隐藏该列

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值