10

I have 3 datagrids that share the same data type. I'd like to configure the column binding once and have the 3 datagrids share the resource.

e.g.

<DataGrid Grid.Row="1" x:Name="primaryDG" ItemsSource="{Binding Path=dgSource AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Width="Auto" Header="Column 1" Binding="{Binding Path=Col1}"/>
        <DataGridTextColumn Width="Auto" Header="Column 2" Binding="{Binding Path=Col2}"/>
        <DataGridTextColumn Width="Auto" Header="Column 3" Binding="{Binding Path=Col3}"/>
        <DataGridTextColumn Width="Auto" Header="Column 4" Binding="{Binding Path=Col4}"/>
    </DataGrid.Columns>
</DataGrid>

Is there a way to set the ItemsSource for each DataGrid, then use a datatemplate or controltemplate to get the columns?

codersl
  • 2,222
  • 4
  • 30
  • 33
Queso
  • 967
  • 1
  • 12
  • 26
  • 1
    similar question, useful answers: http://stackoverflow.com/questions/5716123/wpf-datagrid-columns-in-style-or-resource – surfen Dec 02 '11 at 12:20

3 Answers3

5

Yes... two ways. One you can simply add a style for DataGrid that sets the columns like this...

<Style x:Key="MyColumnDefsStyle" x:Shared="True" TargetType="DataGrid">
    <Setter Property="Columns">
        <Setter.Value>
             <DataGridTextColumn Width="Auto" Header="Column 1" Binding="{Binding Path=Col1}"/>
             <DataGridTextColumn Width="Auto" Header="Column 2" Binding="{Binding Path=Col2}"/>
             <DataGridTextColumn Width="Auto" Header="Column 3" Binding="{Binding Path=Col3}"/>
             <DataGridTextColumn Width="Auto" Header="Column 4" Binding="{Binding Path=Col4}"/>
        </Setter.Value>
    </Setter>
</Style>

<DataGrid Style="{StaticResource MyColumnDefsStyle}" ItemsSource="{Binding Foo1}" />
<DataGrid Style="{StaticResource MyColumnDefsStyle}" ItemsSource="{Binding Foo2}" />
<DataGrid Style="{StaticResource MyColumnDefsStyle}" ItemsSource="{Binding Foo3}" />

That works but represents a problem if you are applying it to multiple grids that themselves may already be using a style.

In that case, the other, more flexible way works better. This however requires creating a XAML-friendly classes to represent an ObservableCollection<DataGridColumn> (although you technically only said columns, I like to be complete myself so I'd also do one for the rows) Then add them in a place you can reference in the XAML namespaces. (I call mine xmlns:dge for 'DataGridEnhancements') You then use it like this:

In the code somwhere (I'd make it accessible app-wide)...

public class DataGridRowsCollection : ObservableCollection<DataGridRow>{}
public class DataGridColumnsCollection : ObservableCollection<DataGridColumn>{}

Then in the resources...

<dge:DataGridColumnsCollection x:Key="MyColumnDefs" x:Shared="True">
    <DataGridTextColumn Width="Auto" Header="Column 1" Binding="{Binding Path=Col1}"/>
    <DataGridTextColumn Width="Auto" Header="Column 2" Binding="{Binding Path=Col2}"/>
    <DataGridTextColumn Width="Auto" Header="Column 3" Binding="{Binding Path=Col3}"/>
    <DataGridTextColumn Width="Auto" Header="Column 4" Binding="{Binding Path=Col4}"/>
</dge:DataGridColumnsCollection>

And finally in the XAML...

<DataGrid Columns="{StaticResource MyColumnDefs}" ItemsSource="{Binding Foo1}" />
<DataGrid Columns="{StaticResource MyColumnDefs}" ItemsSource="{Binding Foo2}" />
<DataGrid Columns="{StaticResource MyColumnDefs}" ItemsSource="{Binding Foo3}" />

HTH,

Mark

EDIT: Since you cannot set the DataGrid.Columns property, you need to enhance your DataGridView (as mentioned in the comments). Here is the code for an EnhancedDataGrid:

public class EnhancedDataGrid : DataGrid
    {
        //the dependency property for 'setting' our columns
        public static DependencyProperty SetColumnsProperty = DependencyProperty.Register(
            "SetColumns",
            typeof (ObservableCollection<DataGridColumn>),
            typeof (EnhancedDataGrid),
            new FrameworkPropertyMetadata
            {
                DefaultValue = new ObservableCollection<DataGridColumn>(),
                PropertyChangedCallback = EnhancedDataGrid.SetColumnsChanged,
                AffectsRender = true,
                AffectsMeasure = true,
                AffectsParentMeasure = true,
                IsAnimationProhibited = true,
                DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
            });

        //callback to reset the columns when our dependency property changes
        private static void SetColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var datagrid = (DataGrid) d;

            datagrid.Columns.Clear();
            foreach (var column in (ObservableCollection<DataGridColumn>)e.NewValue)
            {
                datagrid.Columns.Add(column);
            }
        }

        //The dependency property wrapper (so that you can consume it inside your xaml)
        public ObservableCollection<DataGridColumn> SetColumns
        {
            get { return (ObservableCollection<DataGridColumn>) this.GetValue(EnhancedDataGrid.SetColumnsProperty); }
            set { this.SetValue(EnhancedDataGrid.SetColumnsProperty, value); }
        } 
    }

Now you could set the columns with the SetColumns dependency property created in your CustomControl:

<custom:EnhancedDataGrid SetColumns="{StaticResource MyColumnDefs}" ItemsSource="{Binding Foo1}" />
<custom:EnhancedDataGrid SetColumns="{StaticResource MyColumnDefs}" ItemsSource="{Binding Foo2}" />
<custom:EnhancedDataGrid SetColumns="{StaticResource MyColumnDefs}" ItemsSource="{Binding Foo3}" />
George Lanetz
  • 300
  • 5
  • 18
Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
  • 1
    Does it really work? Columns property has no setter: http://msdn.microsoft.com/en-us/library/system.windows.controls.datagrid.columns.aspx – surfen Dec 02 '11 at 12:18
  • 2
    Ooops! I think you're right. Our code worked because we used a custom subclass called EnhancedDataGrid that handled that. It added the setter, but only actually moved over the children. I just changed the class name from our subclass to DataGrid for the example. Thanks for calling this out. Easiest way to do this is to add a second Collection property, and bind their contents internally. Only minor change from this code inside the grid. – Mark A. Donohoe Jan 30 '12 at 04:57
  • Thanks nice solution. Can you show me how to set grid "styles" based on some property on my viewmodel. I have different layout based on the same data – Martin Andersen Mar 14 '14 at 12:57
  • 1
    @MarqueIV I've edited your answer providing the custom control with the dependency property that *resets* the property `DataGrid.Columns`. – QuantumHive Sep 22 '15 at 09:51
  • Since my edit got rejected because it should be provided as a comment/answer, I've posted the *edit* as an answer that builds upon this solution. – QuantumHive Sep 22 '15 at 10:31
  • 1
    You may use attached property instead of custom grid. https://stackoverflow.com/a/4379965/991267 – Der_Meister Jan 11 '18 at 06:28
2

This answer is based on MarquelV's solution. In his answer (and in the comments) he mentions a Custom Control named EnhancedDataGrid, where he provides logic to set the DataGrid.Columns property. Here is the code for an EnhancedDataGrid:

public class EnhancedDataGrid : DataGrid
    {
        //the dependency property for 'setting' our columns
        public static DependencyProperty SetColumnsProperty = DependencyProperty.Register(
            "SetColumns",
            typeof (ObservableCollection<DataGridColumn>),
            typeof (EnhancedDataGrid),
            new FrameworkPropertyMetadata
            {
                DefaultValue = new ObservableCollection<DataGridColumn>(),
                PropertyChangedCallback = EnhancedDataGrid.SetColumnsChanged,
                AffectsRender = true,
                AffectsMeasure = true,
                AffectsParentMeasure = true,
                IsAnimationProhibited = true,
                DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
            });

        //callback to reset the columns when our dependency property changes
        private static void SetColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var datagrid = (DataGrid) d;

            datagrid.Columns.Clear();
            foreach (var column in (ObservableCollection<DataGridColumn>)e.NewValue)
            {
                datagrid.Columns.Add(column);
            }
        }

        //The dependency property wrapper (so that you can consume it inside your xaml)
        public ObservableCollection<DataGridColumn> SetColumns
        {
            get { return (ObservableCollection<DataGridColumn>) this.GetValue(EnhancedDataGrid.SetColumnsProperty); }
            set { this.SetValue(EnhancedDataGrid.SetColumnsProperty, value); }
        } 
    }

Now you could set the columns with the SetColumns dependency property created in your CustomControl:

<custom:EnhancedDataGrid SetColumns="{StaticResource MyColumnDefs}" ItemsSource="{Binding Foo1}" />
<custom:EnhancedDataGrid SetColumns="{StaticResource MyColumnDefs}" ItemsSource="{Binding Foo2}" />
<custom:EnhancedDataGrid SetColumns="{StaticResource MyColumnDefs}" ItemsSource="{Binding Foo3}" />
QuantumHive
  • 5,613
  • 4
  • 33
  • 55
1

You can create a custom control just to wrap the data grid and pass data to it. The control will set the data on a grid.

Vitalik
  • 2,724
  • 4
  • 32
  • 43
  • This only works if the entire grid is the same between all iterations, which may or may not be the case. Plus, it limits being able to style them with control templates or other styles as well since you've wrapped it in a UserControl. @Queso specifically asked about just the columns which is why I suggested the above solution which targets only that and can be used anywhere you're using a grid, including within a UserControl. – Mark A. Donohoe Jun 01 '11 at 00:36