Binding Visibility for DataGridColumn in WPF
Asked Answered
H

4

98

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?

Hod answered 27/2, 2014 at 15:58 Comment(0)
S
218

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>
Schizont answered 27/2, 2014 at 16:50 Comment(17)
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.Hod
@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.Schizont
+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.Bezonian
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.Schizont
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.Bezonian
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...Hod
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.Dulse
@Dulse - May be I didn't phrase it properly but intent was same as you mentioned. Updated. Thanks.Schizont
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.Facial
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?Convenient
I liked this answer so much I blogged it: technical-recipes.com/2017/…Presbyterate
@Hod is there a way to hide the blue underline warnings this creates in the xaml?Brownnose
@Brownnose 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.Hod
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.Lobell
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 <DataGrid.ColumnHeaderStyle> and <DataGrid.Columns>. 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!Bigeye
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.Norri
Brilliant solution. Is there a way to do this part in the C# code-behind? <DataGridTextColumn Visibility="{Binding Data.MyColumnVisibility, Source={StaticResource proxy}}"/?Indiana
R
27
<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();
    }
}
Rhody answered 8/8, 2016 at 12:11 Comment(3)
I guess that has already been suggested a year ago. Too late.Hod
If you want to print the class of the current DataContext, use this: <TextBlock Text="{Binding DataContext, Source={StaticResource ProxyElement}}"></TextBlock>Bylaw
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.Costumer
S
5

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>
Sabadilla answered 20/2, 2022 at 16:10 Comment(4)
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.Physiologist
@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.Brumbaugh
I probably wasn't aware of the second option in the accepted answer, when I shared this solution. But I agree with @Brumbaugh that there is no really added value here.Sabadilla
@Brumbaugh 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.Physiologist
D
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

Deoxyribonuclease answered 3/2, 2023 at 6:7 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.