Home > Back-end >  Wpf - Create a control / element as a resource and use it in XAML
Wpf - Create a control / element as a resource and use it in XAML

Time:01-31

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>

CodePudding user response:

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).

CodePudding user response:

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
  • Related