Udostępnij za pośrednictwem


Forwarding the DataGrid’s DataContext to its’ columns..

DataContext is a very handy inherited property on any WPF application..     
Most of the time, I set DataContext near the root on the [logical] tree, and let the inherited DataContext do its magic to bind the rest of the scene.

I recently tried to bind a DataGridColumn to its inherited DataContext (via its datagrid container) and got a very surprising answer on the output trace window:

“System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element…”

What is happening here?    
The Columns collection is just a property in the Datagrid; this collection is not in the logical (or visual) tree, therefore the DataContext is not being inherited, which leads to there being nothing to bind to.

I needed the functionality so I had to create a workaround.. With out much thought I decided to: 

  1. Listen for DataContextChanged in the DataGrid
  2. When DataContext changes,  forward the new value to the DataGridColumns in the datagrid.
  3. Bind properties on the DataGridColumn to this ‘forwarded’ DataContext  ( as I originally intended)

To get it done,  I did not inherit from DataGrid and create a new class..  Instead i used the richness of WPF’s property system to pull a 1,2 punch:

  1. Override DataGrid’s DataContext metadata and listen for changes in it…
  2. Add a FrameworkElement.DataContextProperty to DataGridColumn …

Code looks like this:

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

The OnDataContextChanged callback simply forwards the DataContext from DataGrid to its columns:

 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 );         }     } }

That is it. Now we are ready to databind to DataGridColumns.

<dg:DataGridTextColumn Binding="{BindingA}"

                    Visibility="{Binding ElementName=ShowA,Path=IsChecked ,

                   Converter={StaticResource BoolToVisConverter}}" />

 

You can download source code for a small sample from here.

DataGridColumnsAll The project has 3 checkboxes that are databound to a viewmodel…    The DataGrid’s column Visibility is databound to this same viewmodel,  if the checkboxes are checked ,the respective column is visible.. if unchecked, it is collapsed.. DataGridColumnsnob

A few more thoughts on DataGridColumn not being in the tree ..

  1. Binding to other UIElements via ElementName will not work because there is no tree.  

  2. Binding to a  StaticResource works fine..

  3. Binding to x:Static  will work fine too.

     

Happy datagrid coding..  Again, there is source here.

Comments