C# – How to use binding in a WPF DataTemplate that is inside a Style

bindingcnettemplateswpf

I am new to WPF data binding / style / templates…
I am trying to apply a set of property values to buttons by using a Style. The style binds to fields of a class. As you can see, this works fine for the BackColor property. But when I try to set the Text of a TextBlock, it does not work (neither do I get a binding error ). My ultimate goal is to also be able to set an Image as the content.

When I do not use a DataTemplate, and use SetterProperty="Content" instead of "ContentTemplate", it will work for one button, but when adding a second button it gives me a runtime error "Specified element is already the logical child of another element. Disconnect it first."

What am I missing here? What do I put into "TextBlock Text="???"

btw. I would like to move the style to a global scope once it works so I do not want to use anything that explicitly refers to MyClass

<Window.Resources>
    <Style TargetType="Button" x:Key="MyStyle">
        <Setter Property="Background" Value="{Binding BackColor}"/>
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="XYZ-"/>
                        <TextBlock Text="{Binding Text}"/>
                    </StackPanel>                        
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>
<StackPanel Orientation="Horizontal" Height="30">
    <Button Style="{StaticResource MyStyle}" DataContext="{Binding Action1}"/>
    <Button Style="{StaticResource MyStyle}" DataContext="{Binding Action1}"/>
    <Button Style="{StaticResource MyStyle}" DataContext="{Binding Action2}"/>
    <Button Style="{StaticResource MyStyle}" DataContext="{Binding Action2}"/>
</StackPanel>

and

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        DataContext = this;

        Action1 = new MyClass() { Text = "ACTION1", BackColor = new SolidColorBrush(Colors.Red) };
        Action2 = new MyClass() { Text = "ACTION2", BackColor = new SolidColorBrush(Colors.Green) };
    }
    public MyClass Action1{get; private set;}
    public MyClass Action2{get; private set;}
}

public class MyClass
{
    public string Text { get; set; }
    public Brush BackColor { get; set; }
}

Best Answer

In your original question, you needed

<Setter Property="Background" Value="{Binding BackColor}"/>
<Setter Property="Content" Value="{Binding Text}"/>

Now you need to use a relative source binding

<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, 
           AncestorType=Button}, Path=DataContext.Text}"/>

However you may be better off using an ItemsControl as follows

Xaml

<Page.Resources>
    <DataTemplate x:Key="ItemTemplate" DataType="{x:Type Samples:MyClass}">
        <Button Background="{Binding BackColor}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="XYZ-"/>
                <TextBlock Text="{Binding Text}"/>
            </StackPanel>
        </Button>
    </DataTemplate>
  <ItemsPanelTemplate x:Key="ItemsPanelTemplate">
        <StackPanel Orientation="Horizontal"/>
    </ItemsPanelTemplate>
</Page.Resources>
<Page.DataContext>
    <Samples:DataTemplateItemsControlViewModel/>
</Page.DataContext>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <ItemsControl 
        ItemsSource="{Binding Items}" 
        ItemsPanel="{StaticResource ItemsPanelTemplate}"
        ItemTemplate="{StaticResource ItemTemplate}"/>
</Grid>

C#

public class DataTemplateItemsControlViewModel
{
    public DataTemplateItemsControlViewModel()
    {
        Items =
            new Collection<MyClass>
                {
                    new MyClass
                        {
                            Text = "ACTION1", 
                            BackColor = new SolidColorBrush(Colors.Red)
                        },
                    new MyClass
                        {
                            Text = "ACTION2", 
                            BackColor = new SolidColorBrush(Colors.Blue)
                        },
                    new MyClass
                        {
                            Text = "ACTION3", 
                            BackColor = new SolidColorBrush(Colors.Green)
                        },
                    new MyClass
                        {
                            Text = "ACTION4", 
                            BackColor = new SolidColorBrush(Colors.Yellow)
                        },
                };
    }

    public IList<MyClass> Items { get; private set; }
}