wpfxamldatagriddatagridcolumn

Wpf - Create a control / element as a resource and use it in XAML


I have multiple DataGrid objects in different tabs of WPF window that share the same columns. I can define a column as a resource like this:

<TabControl.Resources>
    <DataGridTextColumn x:Key="NameGridColumn" Binding="{Binding Name}" IsReadOnly="True" Width="*">
        <DataGridTextColumn.Header>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Name" VerticalAlignment="Center"/>
                <Button Command="{Binding SortByNameCommand}" Content="▲" Background="Transparent" BorderThickness="0" Margin="5 0 0 0"/>
            </StackPanel>
        </DataGridTextColumn.Header>
    </DataGridTextColumn>
</TabControl.Resources>

But how can I then use these resource columns in a DataGrid?

<TabItem Header="Tab 1">
    <DataGrid ItemsSource="{Binding ItemsCollection}" CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="False" AutoGenerateColumns="False">
        <DataGrid.Columns>
            
            <????>
            <!-- notice that I have many columns, not just one -->

        </DataGrid.Columns>
    </DataGrid>
</TabItem>

Solution

  • Not the ideal answer, but fairly good.

    This is an answer specific for data grid column headers (the columns you define are not actual controls, but representations for controls that will be generated later). If you want an answer for actual controls in general, you can resort to this answer


    You can use a DataTemplate to define the headers:

    <TabControl.Resources>
        <!--Converter to change arrow buttons appearance-->
        <local:EnabledToBrushConverter x:Key="EnabledToBrushConverter"/>
        
        <!--Style for all arrow buttons-->
        <Style TargetType="Button">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="Margin" Value="5 0 0 0"/>
        </Style>
        
        <!-- each data template is a header -->
        <DataTemplate x:Key="NameGridHeader">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Name" VerticalAlignment="Center"/>
                <Button Command="{Binding Path=DataContext.SortByNameCommand, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}" Content="▲" 
                        Foreground="{Binding Path=DataContext.IsSortByName, Converter={StaticResource EnabledToBrushConverter}, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}"/>
            </StackPanel>
        </DataTemplate>
    
        <DataTemplate x:Key="AnotherHeader">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Another value" VerticalAlignment="Center"/>
                <Button Command="{Binding Path=DataContext.SortByAnotherValueCommand, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}" Content="▼"
                        Foreground="{Binding Path=DataContext.IsSortByAnotherValue, Converter={StaticResource EnabledToBrushConverter}, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}"/>
            </StackPanel>
        </DataTemplate>
    </TabControl.Resources>
    

    And you can use the HeaderTemplate property of a DataGridTextColumn or a DataGridTemplateColumn.

    <DataGrid.Columns>
        <DataGridTextColumn Header="Type" Binding="{Binding ItemType}" IsReadOnly="True"/>
        <DataGridTextColumn HeaderTemplate="{StaticResource NameGridHeader}" Binding="{Binding Name}" IsReadOnly="True" Width="*"/>
        <DataGridTextColumn HeaderTemplate="{StaticResource AnotherHeader}" Binding="{Binding AnotherValueString}" IsReadOnly="True"/>
    </DataGrid.Columns>
    
    <!-- Define as many datagrids as you like using the same column header templates -->
    

    The downsides are: