93

How can I hide a column in a WPF DataGrid through a Binding?

This is what I did:

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

And this is what I got (besides the column still visible):

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

How to fix the binding?

ASh
  • 34,632
  • 9
  • 60
  • 82
ygoe
  • 18,655
  • 23
  • 113
  • 210

4 Answers4

207

First of all, DataGridTextColumn (or any other supported dataGrid column) does not lie in the Visual tree of the DataGrid. Hence, by default it doesn't inherit the DataContext of the DataGrid. However, it works for Binding DP only and for no other DP's on DataGridColumn.

Since they don't lie in the same VisualTree, any attempt to get the DataContext using RelativeSource won't work as well because DataGridTextColumn is unable to traverse up to the DataGrid.

There are two other ways to achieve this though:


First using a Freezable class. Freezable objects can inherit the DataContext even when they’re not in the visual or logical tree –We can take advantage of that.

First, create a class inheriting from Freezable and Data DP which we can use to bind in 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));
}

Now, add an instance of it in DataGrid resources so that it can inherit the DataGrid's DataContext and can bind with its 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>

Second, you can refer to any UI element in XAML using ElementName or x:Reference. However, ElementName works only in the same visual tree, whereas x:Reference doesn't have such constraints.

So, we can use that as well to our advantage. Create a dummy FrameworkElement in XAML with Visibility set to collapsed. The FrameworkElement will inherit the DataContext from its parent container, which can be a Window or UserControl.

And can use that in 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>
Jim Simson
  • 2,774
  • 3
  • 22
  • 30
Rohit Vats
  • 79,502
  • 12
  • 161
  • 185
  • 5
    I like this second approach. It's easy to write and I have another control of the same visibility already so I can just give that an `x:Name` and reference to its `Visibility` property. Not really straight-forward, more turning sideways along the way, but still simple. I guess, when binding to the referenced element's DataContext property, you "hijack" the other element to share its DataContext with the otherwise unreachable DataGridColumn, right? The dummyElement is just the bridge. – ygoe Feb 27 '14 at 17:04
  • 2
    @LonelyPixel - Yeah you got it right. I try to hijack DataContext from its DataGrid sibling child since they both share same DataContext unless set explicitly. I could have used x:Reference with DataGrid itself but that would have result in cyclic dependency. – Rohit Vats Feb 27 '14 at 17:06
  • 1
    +1 for your answer. I'm sorry, I misunderstood the question. By about the use of `x:Reference` - in WPF 4.0, at least for the Visual Studio 2010 may still appear exception: `Service provider is missing the INameResolver service`, it can be ignored. And as I understand it, it was fixed in WPF 4.5. – Anatoliy Nikolaev Feb 27 '14 at 17:28
  • 3
    Personally if you ask me, I like first approach. Overhead is just to create a class but once you have it in your kitty, life becomes much easy coding in XAML. I do use it more often. – Rohit Vats Feb 27 '14 at 17:31
  • Primarily to @LonelyPixel him to know this feature. I agree with you, better once just come up/create a class and use everywhere for this task. – Anatoliy Nikolaev Feb 27 '14 at 17:36
  • Yes, the `BindingProxy` works fine, too. Now that I've added it to the project, I think I'll keep it. Thank you! • About the designer exception: After reloading the solution today, I've also seen this. But I'm used to it. There's blue underlines everywhere on `StaticResource` and I know that at some point I can just hide the preview panel because it's failing hopelessly. I write XAML by hand anyway. That's what I like it for - unlike Windows Forms designer which may mess up the entire windiw sometimes... – ygoe Feb 28 '14 at 10:18
  • The DataGrid columns does not live in any Visual Tree, they are not visual elements (they don't derive from `System.Windows.Media.Visual`). They live in the **logical** tree. The sentence "dataGrid columns doesn't lie in same Visual tree as that of DataGrid" is confusing. – Maxence Aug 07 '15 at 17:20
  • @Maxence - May be I didn't phrase it properly but intent was same as you mentioned. Updated. Thanks. – Rohit Vats Aug 08 '15 at 20:27
  • 1
    A clarification: it's not quite true that "*you can refer to any UI element in XAML using ... `x:Reference`*". I wanted to bind to a property of the `UserControl` which contains the `DataGrid`, but using `x:Reference` to its name gave me an exception due to circular reference. It *does* work to use a `FrameworkElement` which binds to the `UserControl` using `ElementName`, and then to bind the `FrameworkElement` using `x:Reference`. tl;dr: the `FrameworkElement` is necessary. – Peter Taylor Aug 08 '16 at 08:07
  • 1
    When I hide my column through my bound ViewModel I see this in VS' Output window, repeated for each row in the DataGrid: "System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.DataGridRow', AncestorLevel='1''. BindingExpression:Path=(0); DataItem=null; target element is 'DataGridCell' (Name=''); target property is 'SelectionUnit' (type 'DataGridSelectionUnit')" - any idea? – Dai Mar 31 '17 at 08:58
  • 1
    I liked this answer so much I blogged it: http://www.technical-recipes.com/2017/binding-the-visibility-of-datagridcolumn-in-wpf-mvvm/ – AndyUK Sep 22 '17 at 10:12
  • @ygoe is there a way to hide the blue underline warnings this creates in the xaml? – JMIII Jul 15 '19 at 14:36
  • 2
    @JMIII Don't know, I'm not using this anywhere now. Also, I don't care what the XAML editor understands (it's not a lot) as long as it runs in the end. – ygoe Jul 15 '19 at 20:22
  • 1
    Just a note that the second option doesn't work with hot reload. It took me a while to figure out why it wasn't working. – Neil B Nov 13 '19 at 20:17
  • Voice of experience here--if you do the second option (the one that worked for me), make sure you put dummyElement completely outside of the DataGrid. I accidentally put it between and . Come runtime where I set ItemsSource, I get an InvalidOperationException saying "Items must be empty before using ItemsSource." Sure enough, Items had one item inside it: dummyElement! – Eric Eggers Feb 28 '20 at 16:59
  • What if I want the datacontext of the parent of datagrid, but I want to use option 1 because the only thing in this UserControl is the datagrid and putting it in a stackpanel messed up my styling? I tried RelativeSource on the proxy but it didn't work. – ztorstri Aug 25 '20 at 17:03
  • Brilliant solution. Is there a way to do this part in the C# code-behind? ` – TimB Jul 21 '21 at 13:49
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>

Sample of bound property in view model:

private Visibility _isShowName;

public Visibility IsShowName
{
    get { return _isShowName; }
    set
    {
        _isShowName = value;
        OnPropertyChanged();
    }
}
Contango
  • 76,540
  • 58
  • 260
  • 305
Meysam Chegini
  • 942
  • 12
  • 12
  • I guess that has already been suggested a year ago. Too late. – ygoe Aug 08 '16 at 18:03
  • If you want to print the class of the current DataContext, use this: `` – Contango Nov 15 '18 at 16:30
  • Does not work if the datacontext is actually not static, but might vary. In that case, I get: "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')" when the window is created. – J S Apr 27 '20 at 05:44
3

Another easy solution I like is to add a dummy collapsed FrameworkElement at the same level as the DataGrid. The FrameworkElement can then be used as the Source of the Binding with the x:Reference markup extension.

For example like this:

<FrameworkElement x:Name="FrameWorkElementProxy" Visibility="Collapsed"/>
<DataGrid>
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="post" 
            Visibility="{Binding DataContext.DataGridColumnVisibility, Source={x:Reference Name=FrameWorkElementProxy}}"/>
    </DataGrid.Columns>
</DataGrid>
STHOH
  • 261
  • 1
  • 12
  • Not sure why this is downvoted. This is literally all you need to make this work; no resource dictionaries or other classes necessary. You just have to make sure your proxy element doesn't have the column as a child, or it will complain at you. – Clonkex May 06 '22 at 03:10
  • @Clonkex: Probably because the accepted answer already mentioned and explained this workaround in 2014 ("second solution"). I don't see the added value of adding the exact same answer twice. – Heinzi Mar 07 '23 at 15:23
  • 1
    I probably wasn't aware of the second option in the accepted answer, when I shared this solution. But I agree with @Heinzi that there is no really added value here. – STHOH Mar 07 '23 at 17:22
  • @Heinzi Ah, it is too. I only saw the first part of the accepted answer and didn't realise this duplicated the second part. The accepted answer should probably be edited to better delineate the first and second parts, but as it stands now this answer technically provides value by making it easier to find this solution. – Clonkex Mar 07 '23 at 22:26
0

Another fast option if you have created the Window/Page/UserControl DataContext object in the XAML like this:

<Window.DataContext>  
    <local:ViewModel x:Name="MyDataContext"/>  
</Window.DataContext>

is that you can add x:Reference using the x:Name of the DataContext object in the Source of the binding:

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

That way you can avoid using Binding DataContext.MyColumnVisibility, and just use Binding MyColumnVisibility

Esteven
  • 1
  • 4