WPF DataGrid: Binding DataGridColumn visibility to ContextMenu MenuItems IsChecked (MVVM)

I want to control DataGrid column visibility through a ContextMenu available to the user by right-clicking the column header. The ContextMenu displays the names of all available columns. I am using MVVM design pattern.

My question is: How do I bind the DataGridColumn's Visibility property to the IsChecked property of a MenuItem located in the ContextMenu.

Some mockup code:

<UserControl.Resources>         
    <ContextMenu x:Key="ColumnHeaderContextMenu">  
        <MenuItem Header="Menu Item..1" IsCheckable="True" />  
    </ContextMenu>  
    <Style x:Key="ColumnHeaderStyle" 
           TargetType="{x:Type toolkit:DataGridColumnHeader}">  
        <Setter Property="ContextMenu" 
                Value="{StaticResource ColumnHeaderContextMenu}" />  
    </Style>  
    <BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />  
</UserControl.Resources>  

...flaf flaf flaf

<toolkit:DataGrid x:Name="MyGrid" AutoGenerateColumns="False" 
    ItemsSource="{Binding MyCollection, Mode=Default}" 
    EnableColumnVirtualization="True" IsReadOnly="True" 
    ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}">  
    <toolkit:DataGrid.Columns>  
        <toolkit:DataGridTextColumn Binding="{Binding Path=MyEntry}" 
            Header="MyEntry" Visibility="{Binding IsChecked, Converter=
                {StaticResource booleanToVisibilityConverter}.... />
    </toolkit:DataGrid.Columns>     
</toolkit:DataGrid>  

If I am being unclear please let me know and I will attempt to elaborate.

Cheers,

13.10.2009 15:12:10
6 ОТВЕТОВ
РЕШЕНИЕ

I just wrote a blog post on this topic. It allows DataGridColumns to be shown or hidden through a ContextMenu that is accessible by right-clicking any column header. This task is accomplished purely through attached properties so it is MVVM-compliant.

See blog post

19
11.01.2011 22:22:25
Just looked through it and it looks solid. I would give you a vote up but lack 1 in reputation :)
Fubzot 18.01.2011 07:42:12
This worked beautifully!! Awesome stuff. Now I need to study it in detail once my deadline is over :)
BloggerDude 15.08.2012 16:20:02
SO rules suggest that linking to a blog rather than posting the explicit content is not ideal. Can you actually answer the Q here?
Webreaper 23.08.2013 06:41:08

I did try to ge this to bind to the ContextMenu using 'ElementName', but in the end, got it work using Properties in the VM, e.g.

bool _isHidden;
public bool IsHidden
{
  get { return _isHidden; }
  set
  {
    if (value != _isHidden)
    {
      _isHidden = value;
      RaisePropertyChanged("IsHidden");
      RaisePropertyChanged("IsVisible");
    }
  }
}

public Visibility IsVisible
{
  get { return IsHidden ? Visibility.Hidden : Visibility.Visible; }
}

and in the XAML:

<Window.ContextMenu>
  <ContextMenu>
    <MenuItem Header="Hidden" IsCheckable="True" IsChecked="{Binding IsHidden}" />
  </ContextMenu>
</Window.ContextMenu>

<toolkit:DataGrid x:Name="MyGrid" AutoGenerateColumns="False" ItemsSource="{Binding MyCollection, Mode=Default}" EnableColumnVirtualization="True" IsReadOnly="True" ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}">
  <toolkit:DataGrid.Columns>
    <toolkit:DataGridTextColumn Binding="{Binding Path=MyEntry}" Header="MyEntry" Visibility="{Binding Path=IsVisible, Mode=OneWay}" />
  </toolkit:DataGrid.Columns>
</toolkit:DataGrid>
-1
13.10.2009 20:30:38
Hi Ian could you elaborate it? I have a datagrid which binds to collection. I want to hide column (visibility) by checking boolean property. E.g Class ContextClass { CollectionForGrid; IsActive; } I am binding CollectionForGrid to Grid & I want to set visiblity of a column base on IsActive property.
RockWorld 22.04.2010 13:29:53
@Rakesh - my best advice would be to open a new question, and I (along with the rest of the SO community) will try and answer it for you :)
kiwipom 26.04.2010 07:32:41

Ok, this has been quite the exercise for a WPF n00b.

IanR thanks for the suggestion I used a similar aproach but it dosent take you all the way.

Here is what I have come up with if anyone can find a more consistent way of doing it I will appreciate any comments:

Impediments:

  1. DataGridColumnHeader does not support a context menu. Therefore the context menu needs to be applied as a Style.

  2. The contextmenu has its own datacontext so we have to use findancestor to link it to the ViewModels datacontext.

  3. ATM the DataGrid control does not parse its datacontext to its Columns. This could be solved in codebehind however we are using the MVVM pattern so I decided to follow jamiers approach

Solution:

Place the following two blocks of code in Window.Resources

    <Style x:Key="ColumnHeaderStyle"
           TargetType="{x:Type toolkit:DataGridColumnHeader}">
        <Setter Property="ContextMenu" 
                Value="{StaticResource ColumnHeaderContextMenu}" />
    </Style>  

    <ContextMenu x:Key="ColumnHeaderContextMenu">
        <MenuItem x:Name="MyMenuItem"
                  IsCheckable="True"
                  IsChecked="{Binding DataContext.IsHidden, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGrid}}}"/>
    </ContextMenu>

The datagrid then looks something like this in XAML

        <toolkit:DataGrid x:Name="MyGrid"
                          AutoGenerateColumns="False"
                          ItemsSource="{Binding SampleCollection, Mode=Default}"
                          EnableColumnVirtualization="True"
                          IsReadOnly="True"
                          ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}">
            <toolkit:DataGrid.Columns>
                <toolkit:DataGridTextColumn Binding="{Binding Path=SamplingUser}"
                                            Header="{Binding (FrameworkElement.DataContext).IsHidden, RelativeSource={x:Static RelativeSource.Self}}"
                                            Visibility="{Binding (FrameworkElement.DataContext).IsHidden,
                                                RelativeSource={x:Static RelativeSource.Self},
                                                Converter={StaticResource booleanToVisibilityConverter}}"/>

So the visibility property on the DataGridColumn and the ischeked property are both databound to the IsHidden property on the viewModel.

In the ViewModel:

    public bool IsHidden
    {
        get { return isHidden; }
        set 
        { if (value != isHidden)
            {
                isHidden = value;
                OnPropertyChanged("IsHidden");
                OnPropertyChanged("IsVisible");
            }
        }
    }  

The Helper class defined by Jaimer:

class DataGridSupport
{
    static DataGridSupport() 
    {

        DependencyProperty dp = FrameworkElement.DataContextProperty.AddOwner(typeof(DataGridColumn)); 
        FrameworkElement.DataContextProperty.OverrideMetadata ( typeof(DataGrid), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnDataContextChanged)));

    }

    public static void OnDataContextChanged ( DependencyObject d, DependencyPropertyChangedEventArgs e)
    { 
        DataGrid grid = d as DataGrid ; 
        if ( grid != null  ) 
        {                 
            foreach ( DataGridColumn col in grid.Columns ) 
            { 
                col.SetValue ( FrameworkElement.DataContextProperty,  e.NewValue ); 
            } 
        } 
    }
}

Instanciated in the viewmodel (just to show done through Unity in real project)

    private static DataGridSupport dc = new DataGridSupport();

Cheers,

1
16.10.2009 09:31:07

Instead of booleanToVisibilityConverter you can use x:static

<Setter TargetName="UIElement"  Property="UIElement.Visibility" Value="x:Static Visibility.Hidden" />

Statics in XAML: http://msdn.microsoft.com/en-us/library/ms742135.aspx

0
13.05.2010 22:17:32
Voted up only because the previous voter who voted this answer down did not provide any explanation regarding the voting down reason.
Ahmad 13.05.2015 14:08:14

I know this is a bit old. But I was looking at doing this and this post is much simpler: http://iimaginec.wordpress.com/2011/07/25/binding-wpf-toolkit%E2%80%99s-datagridcolumn-to-a-viewmodel-datacontext-propogation-for-datagrid-columns-the-mvvm-way-to-interact-with-datagridcolumn/

All you need to do is set DataContext on the Columns and then bind Visibility to your ViewModel as per normal! :) Simple and effective

6
25.07.2011 10:45:51
Works great for me in 3.5 + WPF Toolkit
Mike Rowley 10.08.2011 18:07:48
+1 That is the best explanation of the pattern that I've seen, thanks.
briantyler 13.02.2013 10:25:06

I've been looking for a generic, XAML (i.e., no code-behind), automatic and simple example of a column chooser context menu that binds to a WPF DataGrid column header. I've read literally hundreds of articles, but none of them seem to do exactly the right thing, or they're no generic enough. So here's what I think is the best combined solution:

First, put these in the resource dictionary. I'll leave it as an exercise to the reader to write the Visibility/Boolean converter to ensure the checkboxes check when the column is visible and vice-versa. Note that by defining x:Shared="False" for the context menu resource, it'll get instance-specific state which means that you can use this single template/resource for all your datagrids and they'll all maintain their own state.

<Converters:VisiblityToInverseBooleanConverter x:Key="VisiblityToInverseBooleanConverter"/>

<ContextMenu x:Key="ColumnChooserMenu" x:Shared="False"
             DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}" 
             ItemsSource="{Binding Columns, RelativeSource={RelativeSource AncestorType={x:Type sdk:DataGrid}}}">
    <ContextMenu.ItemContainerStyle>
        <Style TargetType="MenuItem">
            <Setter Property="Header" Value="{Binding Header}"/>
            <Setter Property="AutomationProperties.Name" Value="{Binding Header}"/>
            <Setter Property="IsCheckable" Value="True" />
            <Setter Property="IsChecked" Value="{Binding Visibility, Mode=TwoWay, Converter={StaticResource VisiblityToInverseBooleanConverter}}" />
        </Style>
    </ContextMenu.ItemContainerStyle>
</ContextMenu>

<Style x:Key="ColumnHeaderStyle" TargetType="{x:Type Primitives:DataGridColumnHeader}">
    <Setter Property="ContextMenu" Value="{StaticResource ColumnChooserMenu}" />
</Style>

<ContextMenu x:Key="GridItemsContextMenu" >
    <MenuItem Header="Launch Do Some other action"/>
</ContextMenu>

Then define the DataGrid as follows (where OrdersQuery is some data source exposed by the View-model):

<sdk:DataGrid ItemsSource="{Binding OrdersQuery}"
              AutoGenerateColumns="True" 
              ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}"
              ContextMenu="{StaticResource GridItemsContextMenu}">

  <!-- rest of datagrid stuff goes here -->

</sdk:DataGrid>

This will give you the following:

  1. A context menu bound to the column headings which acts as a column chooser.
  2. A context menu bound to the items in the grid (to perform actions on the items themselves - again, the binding of the actions is an exercise for the reader).

Hope this helps people who've been looking for an example like this.

15
1.05.2020 21:35:15
This answer should be higher. Simple, elegant solution.
Gordon True 28.01.2015 20:40:23
Very neat solution! However one note: it only works if columns are auto generated. Obviously of course, but I it's worth mentioning.
Herman Cordes 27.10.2015 07:32:40
Are you sure? Pretty certain it works for auto-generated or explicitly-generated columns (but haven't tried it for a couple of years so could be wrong).
Webreaper 28.10.2015 09:45:40
It works for me and I am specifying my columns manually in XAML
Owen 31.08.2018 10:20:14
Worked like a charm :). Thanks for sharing. This is way better than accepted answer.
Vaibhav Gawali 19.02.2020 09:10:20