51

.Net 3.5

I know that the columns doesn't inherit the datacontext and by reading other posts i thought this would work:

Visibility="{Binding RelativeSource={x:Static RelativeSource.Self},
                     Path=(FrameworkElement.DataContext).IsColumnNameVisible,
                     Converter={StaticResource boolToVisConverter}}"

However of course it doesn't.. The output window does not complain, it seems that the resource i found but the viewmodel property is newer called.

This is the entire DG :

<tk:DataGrid                                        
            VirtualizingStackPanel.IsVirtualizing="False"                                        
            Grid.Column="0"
            AlternationCount="2"
            AreRowDetailsFrozen="True"
            AutoGenerateColumns="False"
            Background="Transparent"
            BorderThickness="0"
            CanUserAddRows="False"
            CanUserReorderColumns="True"
            CanUserResizeRows="False"
            GridLinesVisibility="None"
            ItemsSource="{Binding Employees}"
            SelectionMode="Single"
            ColumnHeaderStyle="{StaticResource columnHeaderStyle}"
            RowHeaderStyle="{StaticResource rowHeaderStyle}"
            CellStyle="{StaticResource cellStyle}"
            RowStyle="{StaticResource rowStyle}" 
            ContextMenu="{StaticResource columnHeaderContextMenu}">
    <tk:DataGrid.Resources>
        <ContextMenu x:Key="columnHeaderContextMenu" ItemsSource="{Binding ColumnHeaderContextMenuItems}" />
        <Style TargetType="{x:Type ScrollBar}">
            <Setter Property="Background" Value="Transparent"/>
        </Style>                                    
        <Style TargetType="{x:Type tk:DataGridColumnHeader}">
            <Setter Property="Background" Value="Transparent"/>
        </Style>
    </tk:DataGrid.Resources>
    <tk:DataGrid.Triggers>
        <EventTrigger RoutedEvent="tk:DataGridRow.MouseDoubleClick">
            <EventTrigger.Actions>
                <BeginStoryboard Storyboard="{StaticResource showDetailGrid}"/>
            </EventTrigger.Actions>
        </EventTrigger>
    </tk:DataGrid.Triggers>
    <tk:DataGrid.Columns>
        <tk:DataGridTextColumn IsReadOnly="True" Header="test" Binding="{Binding Name, Mode=OneWay}" Visibility="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(FrameworkElement.DataContext).IsColumnNameVisible, Converter={StaticResource boolToVisConverter}}"  />
    </tk:DataGrid.Columns>
</tk:DataGrid>

I have read pretty much every single solution to this problem and nothing works..

H.B.
  • 166,899
  • 29
  • 327
  • 400
jrb
  • 1,708
  • 2
  • 13
  • 20
  • If find your question a little unclear. Are you just trying to make a data column visible or invisible depending on a bound ViewModel property? – ChrisBD Oct 10 '11 at 10:21
  • You change your ContextMenu in `` - no wonder that your window DataContext isn't reachable. – Felix C Oct 10 '11 at 10:26
  • @ ChrisBD : Yes that is the idea. The VM prop is set via the datacontext. – jrb Oct 10 '11 at 11:24
  • @ Felix: What do you mean? Its a contextmenu.. why would that effect the datacontext availability? – jrb Oct 10 '11 at 11:26

1 Answers1

109

DataGridColumns are not part of visual tree so they are not connected to the data context of the DataGrid.

For them to connect together use proxy element approach like this...

  1. Add a proxy FrameworkElement in your ancestor panel's Resources.

  2. Host it into an invisible ContentControl bound to its Content.

  3. Use this ProxyElement as StaticResource for data context source in your visibility binding.

     <StackPanel>
         <StackPanel.Resources>
            <local:BooleanToVisibilityConverter
                   x:Key="BooleanToVisibilityConverter" />
    
            <FrameworkElement x:Key="ProxyElement"
                              DataContext="{Binding}"/>
         </StackPanel.Resources>
         <ContentControl Visibility="Collapsed"
                     Content="{StaticResource ProxyElement}"/>
         <DataGrid AutoGenerateColumns="False">
             <DataGrid.Columns>
                 <DataGridTextColumn
                        Visibility="{Binding DataContext.IsTextColumnVisibile,
                                             Source={StaticResource ProxyElement},
                                             Converter={StaticResource
                                                 BooleanToVisibilityConverter}}"
                        Binding="{Binding Text}"/>
             </DataGrid.Columns>
         </DataGrid>
     </StackPanel> 
    

Apart from DataGridColumn, the above approach also works great to connect DataContext to Popups and ContextMenus (i.e. any element that is not connected to the visual tree).

Silverlight Users

Sadly setting contents of content controls with any framework elements is not allowed in silverlight. So the workaround would be (this is just a guidance code for silverlight) ...

  1. Change the framework element resource to something lightweight like a Textblock. (Silverlight does not allow specifying static resource of FrameworkElement type.)

     <StackPanel.Resources>
         <TextBlock x:Key="MyTextBlock" />
    
  2. Write an attached property to hold text block against the content control.

     <ContentControl Visibility="Collapsed" 
                     local:MyAttachedBehavior.ProxyElement="{StaticResource MyTextBlock}" />
    
  3. In the attached dependency property changed event handler, set the bind the data context of the content control to the text block's.

      private static void OnProxyElementPropertyChanged(
          DependencyObject depObj, DependencyPropertyChangedEventArgs e)
      {
            if (depObj is ContentControl && e.NewValue is TextBlock)
            {
                var binding = new Binding("DataContext");
                binding.Source = depObj;
                binding.Mode = OneWay;
                BindingOperations.SetBinding(
                    (TextBlock)e.NewValue, TextBlock.DataContextProperty, binding);
            }
      }
    

So this way the textblock may not be connected to the visual tree but will probably be aware of the data context changes.

starball
  • 20,030
  • 7
  • 43
  • 238
WPF-it
  • 19,625
  • 8
  • 55
  • 71
  • Got an exeption when deselect and reselect : Specified element is already the logical child of another element. Disconnect it first. Do u know why? – jrb Oct 10 '11 at 13:00
  • Could be your `ContextMenu` ... it can only be attached to one parent. – WPF-it Oct 10 '11 at 13:25
  • Changed the ContextMenu to DataGrid CM not DGColumnHeader CM but it didn't help. I use this on multiple columns if that matters.. – jrb Oct 10 '11 at 13:59
  • Does this work for silverlight as well? FrameworkElement is in System.Windows, and I can't seem to import that as a namespace to the xaml file ... – John Oct 11 '11 at 13:16
  • Instead of `FrameworkElement` use any derived type like `TextBlock`. :) – WPF-it Oct 11 '11 at 13:20
  • The TextBlock worked no problem, but I'm getting an error at runtime, "Failed to assign to property 'System.Windows.Controls.ContentControl.Content'." Here's the relevant code: ... ... Any ideas? – John Oct 11 '11 at 14:18
  • @John, please see my edited reply (I havent tested it though) – WPF-it Oct 11 '11 at 15:59
  • @AngelWPF thanks for looking into that for me! I used this method as total workaround for now (http://mockable.blogspot.com/2010/10/databinding-visibility-of-silverlight.html) but I'd prefer not having to extend base classes for simple functionality so I'll go back and try out yours once I can. – John Oct 11 '11 at 18:36
  • For some reason this only works for the Visibility column, although I don't understand why. For example, binding "Width" has no effect at all. Binding "DisplayIndex" throws an exception, telling me -1 is out of range - even though its correct in the binding. – ShadowChaser Feb 08 '12 at 18:42
  • 3
    @WPF-it can you explain plz why `ContentControl` is required here if we use `ProxyElement` anyway? [This](http://stackoverflow.com/questions/22073740/binding-visibility-for-datagridcolumn-in-wpf) solution uses the similiar approach, but no `ContentControl` is required. – monstr Aug 26 '14 at 07:27
  • 3
    @monstr, Freezable classes hack the `DataContext` ... in our case 'proxyelement' is not Freezable, so the only way it can acquire a `DataContext` if it is hosted within the `VisualTree` which is possible if it is a `Content` of a `ContentControl`. Also for the second solution in your link, the `NameScoping` is not resolved in `Template` like `DataGridColumnHeaderTemplate` so the `ElementName` wouldn't work. The solution I have provided takes care of both situations. – WPF-it Aug 27 '14 at 11:07
  • Hi WPF-it, your approach works perfectly fine and I am able to hide/show column MVVM way. However when I hide column, an extra blank column is shown at the end. I have AutoGenerateColumns set to False & HeaderVisibility set to Column. All column widths are set to Auto (* doesn't help). Do you have any solution/inputs for the extra column? Thanks – RDV Sep 09 '15 at 19:36
  • I made the mistake to think that DataBindings of Visibility (at DataGridColumn) be a subset of items which bound as ItemsSource in the DataGrid. – peter70 Feb 03 '16 at 07:02
  • Thank you a thousand times, this works! I spent good part of the day on this. I hate anyone who created WPF and introduced this kind of bugs practically unsolvable without Google and SO. Once again I have to state that WPF as a technology is only good for writing tutorials, I can't imagine that anyone use it for production apps voluntarily. – lot Jan 27 '21 at 18:34