在WPF中为DataGridColumn绑定可见性

94

如何通过绑定在WPF DataGrid中隐藏列?

这是我所做的:

<DataGridTextColumn Header="Column header"
                    Binding="{Binding ColumnValue}"
                    Width="100"
                    ElementStyle="{StaticResource DataGridRightAlign}"
                    Visibility="{Binding MyColumnVisibility}" />

这是我得到的内容(除了仍可见的列):

System.Windows.Data Error: 2 : 找不到目标元素的统治FrameworkElement或FrameworkContentElement。BindingExpression:Path = MyColumnVisibility; DataItem = null;目标元素为'DataGridTextColumn'(HashCode = 1460142);目标属性为'Visibility'(类型为'Visibility')

如何修复绑定问题?

4个回答

211
首先,DataGridTextColumn(或任何其他支持的dataGrid列)并不在DataGrid的可视树中。因此,默认情况下,它不会继承DataGridDataContext。但是,它仅适用于Binding DP,并且对于DataGridColumn上的其他DP无效。
由于它们不在同一VisualTree中,任何尝试使用RelativeSource获取DataContext都不起作用,因为DataGridTextColumn无法遍历到DataGrid
不过,有两种其他方法可以实现这一点:
第一种是使用一个Freezable类。Freezable对象即使不在可视或逻辑树中也可以继承DataContext-我们可以利用这一点。
首先,创建一个从FreezableData DP继承的类,我们可以在XAML中使用它进行绑定:
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); }
    }

    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object),
                                     typeof(BindingProxy));
}

现在,在DataGrid资源中添加一个它的实例,这样它就可以继承DataGrid的DataContext并可以绑定其Data DP:

    <DataGrid>
        <DataGrid.Resources>
            <local:BindingProxy x:Key="proxy" Data="{Binding}"/>
        </DataGrid.Resources>
        <DataGrid.Columns>
            <DataGridTextColumn Visibility="{Binding Data.MyColumnVisibility,
                                                Source={StaticResource proxy}}"/>
        </DataGrid.Columns>
    </DataGrid>

其次,你可以使用ElementNamex:Reference来引用XAML中的任何UI元素。但是ElementName仅在同一可视树中有效,而x:Reference没有这样的限制。

因此,我们也可以利用它们的优势。在XAML中创建一个Visibility属性设置为collapsed的虚拟FrameworkElement。该FrameworkElement将从其父容器(可以是窗口或用户控件)继承DataContext。

然后可以在DataGrid中使用它:

    <FrameworkElement x:Name="dummyElement" Visibility="Collapsed"/>
    <DataGrid>
        <DataGrid.Columns>
            <DataGridTextColumn Header="Test"
                                Binding="{Binding Name}"
                                Visibility="{Binding DataContext.IsEnable,
                                          Source={x:Reference dummyElement}}"/>
        </DataGrid.Columns>
    </DataGrid>

5
我喜欢这个第二种方法。它很容易编写,我已经有了另一个相同可见性的控件,所以我只需要给它一个 x:Name 并引用其 Visibility 属性即可。虽然不是很直接明了,但还是很简单。我想,当绑定到引用元素的 DataContext 属性时,你会“劫持”其他元素来共享其 DataContext 与无法访问的 DataGridColumn,对吗?dummyElement 就是桥梁。 - ygoe
2
@LonelyPixel - 是的,你说得对。我试图从它的DataGrid兄弟子项中劫持DataContext,因为它们都共享相同的DataContext,除非明确设置。我本可以使用DataGrid本身的x:Reference,但那样会导致循环依赖。 - Rohit Vats
1
很抱歉,我误解了问题。关于使用x:Reference - 在WPF 4.0中,至少对于Visual Studio 2010可能仍会出现异常:Service provider is missing the INameResolver service,但可以忽略它。据我所知,这在WPF 4.5中已经修复。 - Anatoliy Nikolaev
3
就我个人而言,我喜欢第一种方法。虽然需要创建一个类,但是一旦你掌握了它,使用XAML编码会变得更加容易。我经常使用它。 - Rohit Vats
2
@JMIII 不知道,我现在没有在任何地方使用它。此外,只要最终能运行,我不关心XAML编辑器理解的程度(它并不多)。 - ygoe
显示剩余12条评论

26
<Window.Resources>
    <ResourceDictionary>
        <FrameworkElement x:Key="ProxyElement" DataContext="{Binding}" />
    </ResourceDictionary>
</Window.Resources>

<!-- Necessary for binding to resolve: adds reference to ProxyElement to tree.-->
<ContentControl Content="{StaticResource ProxyElement}" Visibility="Collapsed" />
<mch:MCHDataGrid Height="350"
                  AutoGenerateColumns="False"
                  FlowDirection="LeftToRight"
                  ItemsSource="{Binding PayStructures}"
                  SelectedItem="{Binding SelectedItem}">
    <DataGrid.Columns>
         <DataGridTemplateColumn Width="70"
                                 Header="name"
                                 IsReadOnly="True"
                                 Visibility="{Binding DataContext.IsShowName,
                                 Source={StaticResource ProxyElement}}">
             <DataGridTemplateColumn.CellTemplate>
                 <DataTemplate>
                     <TextBlock Text="{Binding FieldName}" />
                 </DataTemplate>
             </DataGridTemplateColumn.CellTemplate>
         </DataGridTemplateColumn>                   
     </DataGrid.Columns>
</mch:MCHDataGrid>

视图模型中绑定属性的示例:

private Visibility _isShowName;

public Visibility IsShowName
{
    get { return _isShowName; }
    set
    {
        _isShowName = value;
        OnPropertyChanged();
    }
}

我猜那已经在一年前被建议过了。太晚了。 - ygoe
如果您想打印当前DataContext的类,请使用以下代码:<TextBlock Text="{Binding DataContext, Source={StaticResource ProxyElement}}"></TextBlock> - Contango
如果数据上下文实际上不是静态的,而可能会变化,那么它就无法工作。在这种情况下,当窗口创建时,我会得到以下错误信息:"System.Windows.Data Error: 3 : Cannot find element that provides DataContext. BindingExpression:(no path); DataItem=null; target element is 'FrameworkElement' (Name='ProxyFrameworkElement'); target property is 'DataContext' (type 'Object')"。 - J S

4

另一个简单的解决方案是在与 DataGrid 相同的级别上添加一个虚拟折叠的 FrameworkElement。可以使用该 FrameworkElement 作为 BindingSource,并使用 x:Reference 标记扩展。

例如:

<FrameworkElement x:Name="FrameWorkElementProxy" Visibility="Collapsed"/>
<DataGrid>
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="post" 
            Visibility="{Binding DataContext.DataGridColumnVisibility, Source={x:Reference Name=FrameWorkElementProxy}}"/>
    </DataGrid.Columns>
</DataGrid>

不确定为什么这个被踩了。这实际上是你需要让它工作的全部内容;不需要资源字典或其他类。你只需要确保你的代理元素没有该列作为子元素,否则它会向你抱怨。 - Clonkex
@Clonkex:可能是因为已经在2014年的“第二种解决方案”中提到并解释了这个解决方法。我不认为添加完全相同的答案两次有任何附加价值。 - Heinzi
1
当我分享这个解决方案的时候,我可能没有意识到接受答案中的第二个选项。但是我同意 @Heinzi 的观点,这里确实没有真正的附加价值。 - STHOH
@Heinzi 啊,是的。我只看到了被接受答案的第一部分,并没有意识到这重复了第二部分。被接受的答案可能需要进行编辑,以更好地区分第一部分和第二部分,但就目前而言,这个答案从技术上讲提供了价值,因为它使得找到这个解决方案变得更容易。 - Clonkex

0
如果您已经在XAML中创建了Window/Page/UserControl DataContext对象,那么另一个快速选项是:
<Window.DataContext>  
    <local:ViewModel x:Name="MyDataContext"/>  
</Window.DataContext>

这是因为您可以使用DataContext对象的x:Name在绑定的Source中添加x:Reference:

<DataGridTextColumn Header="Column header"
                    Binding="{Binding ColumnValue}"
                    Width="100"
                    ElementStyle="{StaticResource DataGridRightAlign}"
                    Visibility="{Binding MyColumnVisibility, Source={x:Reference Name=MyDataContext}}"

这样你就可以避免使用Binding DataContext.MyColumnVisibility,而直接使用Binding MyColumnVisibility


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接