Search code examples
c#wpfxamldatatemplateitemscontrol

Is there a way to inject an Item/DataTemplate into a custom Style or UserControl?


Read update for a solution

I have a small WPF Application with several small game clones such as Minesweeper, Connect 4, Tic Tac Toe among others.

Common for all of these is that they are all a uniform grid of squares, each square is controlled by a button.

For each of these games I have defined a UserControl with a UniformGrid ItemsPanelTemplate in their XAML.

The only place they differ is the DataTemplate used:

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <!-- Button that fit the specific need of the game -->
    </DataTemplate>           
</ItemsControl.ItemTemplate>

To avoid repetition I wanted to create a UserControl which has a DataTemplate dependency property (and has an ItemsControl defined in the XAML named itemscontrol):

public DataTemplate DataTemplate
{
    get => (DataTemplate)GetValue(DataTemplateProperty);
    set => SetValue(DataTemplateProperty, value);
}
public static readonly DependencyProperty DataTemplateProperty =
        DependencyProperty.Register("DataTemplate", typeof(DataTemplate), typeof(BoardGameControl));

public BoardGameControl()
{
    InitializeComponent();
    itemscontrol.ItemTemplate = DataTemplate;
}

Which I tried to use in my application like so:

<controls:BoardGameControl>
    <controls:BoardGameControl.DataTemplate>
        <DataTemplate>
            <Button Content="Hi"/>
        </DataTemplate>
    </controls:BoardGameControl.DataTemplate>
</controls:BoardGameControl>

I have tried some other approaches as well but none have worked.

How can I avoid having to define a new ItemsControl for each game and instead have a UserControl or Style that simply accepts a different Button depending on the situation?

Update

I combined both the solution I marked as Accepted and the comment by @Joe on this post.

Instead of a UserControl I created a Custom Control with my desired properties and then styled in in the Generic.xaml file to my liking. I also removed the DataTemplate property from my Custom Control and instead added DataTemplates in my App.xaml for each different VM.

Below you will find the code behind and style of my new Custom Control.

public class GameBoardControl : Control
{
    public int Columns
    {
        get => (int)GetValue(ColumnsProperty);
        set => SetValue(ColumnsProperty, value);
    }
    public static readonly DependencyProperty ColumnsProperty =
        DependencyProperty.Register(nameof(Columns), typeof(int), typeof(BoardGameControl));

    public int Rows
    {
        get => (int)GetValue(RowsProperty);
        set => SetValue(RowsProperty, value);
    }
    public static readonly DependencyProperty RowsProperty =
        DependencyProperty.Register(nameof(Rows), typeof(int), typeof(BoardGameControl));

    static GameBoardControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(GameBoardControl), new FrameworkPropertyMetadata(typeof(GameBoardControl)));
    }
}

<Style TargetType="{x:Type controls:GameBoardControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type controls:GameBoardControl}">
                <ItemsControl ItemsSource="{Binding Squares}">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <UniformGrid Columns="{Binding Columns, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GameBoardControl}}}"
                                         Rows="{Binding Rows, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GameBoardControl}}}"/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                </ItemsControl>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Note that I always bind the ItemsSource to Squares, I am able to do this since all of my games have an ObservableCollection called Squares where the Square View Models are stored.

Example of a DataTemplate

<DataTemplate DataType="{x:Type mvvmtoolkit:MemorySquareVM}">
        <Button Command="{Binding RelativeSource={RelativeSource AncestorType=controls:GameBoardControl}, Path=DataContext.PressSquareCommand}" 
                CommandParameter="{Binding}"
                Background="{Binding Position, Converter={local:PositionToColorConverter}}"
                Style="{StaticResource MemoryButton}"/>
</DataTemplate>

Solution

  • If I understand correctly what you need, then you need to slightly change the implementation: You should not use value assignment in Code Behind - Instead, you need to use binding in XAML.

    public BoardGameControl()
    {
        InitializeComponent();
        // itemscontrol.ItemTemplate = DataTemplate;
    }
    
    <ItemsControl ItemTemplate="{Binding DataTemplate, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:BoardGameControl}}}">
    

    I also strongly advise in such cases (when properties are added to an element, its behavior is changed, etc.) to use not UserControl, but Custom Control.

    why my implementation did not work?

    Implementation on Sharp:

    public static readonly DependencyProperty DataTemplateProperty =
            DependencyProperty.Register("DataTemplate", typeof(DataTemplate), typeof(BoardGameControl),
             new PropertyMetadata((d, e) => ((BoardGameControl)d).itemscontrol.ItemTemplate = (IEnumerable) e.NewValue));
    
    public BoardGameControl()
    {
        InitializeComponent();
        // itemscontrol.ItemTemplate = DataTemplate;
    }
    

    I would make a Custom Control, however I do not know how to implement the logic of an ItemsControl / UniformGrid myself

    If you work in the Studio, then select "Add" -> "Create element" in the context menu. A combo box will appear and select "Custom Control WPF" from it. You will have a class code derived from Control. Add properties and the logic of behavior you need to it. And the "Themes/Generic.xaml" file will also be created - this is the default theme. This theme will have a template for your element - edit it as you need.