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

69 Views Asked by At

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>
4

There are 4 best solutions below

0
Daniel Möller On BEST ANSWER

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:

  • Cannot declare the column bindings in the template (might be an advantage if you want the same header for different data, though)
  • Cannot declare an entire column (header and cell template), must use separate cell templates (with DataGridTemplateColumn's CellTemplate or CellEditingTemplate) or styles if it's the case
  • Bindings in the headers only work with RelativeSource set to some element above it
0
Anton On

As I know WPF has restriction - UI element can belong only to one parent control. So it seems to you can't reuse one column in different grids.

But you can create UserControl and specify in XAML Grid with your columns. In such case if DataContext will be the same (for example same VM -> you can simple use it in different places of your application).

0
Gilad Waisel On

There is a way to use DataTemplate for the Cell and another one for the Header:

                <DataTemplate x:Key="Head1Template">
                    <TextBlock Text="Product Name"/>
                </DataTemplate>
                <DataTemplate x:Key="Head1CellTemplate">
                    <TextBlock Text="{Binding Product}"/>
                </DataTemplate>

and using it in the DataGrid:

<DataGridTemplateColumn  CellTemplate="{StaticResource Head1CellTemplate}" HeaderTemplate="{StaticResource Head1Template}"/>
0
mm8 On

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

You can't. At least not using pure XAML.

What you can to is to define the entire DataGrid, including all columns, as a resource or a separate control and reuse it like any other control, e.g.:

<local:CustomDataGrid ItemsSource="{Binding ItemsCollection}" />

Another option is to redefine the column(s) in each tab but define the StackPanel as a resource and set the header of each column like this:

<DataGridTextColumn Binding="{Binding Name}" IsReadOnly="True" Width="*"
    Header="{StaticResource theStackPanel}">

You would then define the StackPanel something like this:

<StackPanel x:Key="theStackPanel" x:Shared="false" Orientation="Horizontal">
    <TextBlock Text="Name" VerticalAlignment="Center"/>
    <Button Command="{Binding SortByNameCommand}" Content="▲" Background="Transparent" BorderThickness="0" Margin="5 0 0 0"/>
</StackPanel>

Either way, you cannot add a column resource to <DataGrid.Columns> in XAML. You can do it programmatically though:

dataGrid.Columns.Add(tabControl.Resources["NameGridColumn"] as DataGridColumn);