Style within DataTemplate is only being applied to the last item in ItemsControl?

Scott picture Scott · Mar 2, 2011 · Viewed 13.4k times · Source

In the XAML below, I have an ItemsControl that has three DataObjects.
I use a DataTemplate to display DataObjects as Buttons with an "X" on them.
The Button uses a Style to set its Content.

If the Setter.Value is "X", everything works great!
However, if I change the Setter.Value to a TextBlock whose TextProperty is "X", the X only appears on the last Button (the third DataObject) and the first two Buttons are empty.

Is this a bug, or can anybody explain why this happens?

Note 1) This is a contrived example to isolate the problem being encountered.
Note 2) I've put both Setter.Value options in the code so you can reproduce both the successful and unsuccessful cases just by having one of them commented out.
Note 3) It appears, this problem is specific to Setters for the 'Content' property. If I use a Setter for the Background property, it correctly applies to all of the DataObjects.

<Grid>
    <Grid.Resources>
        <Style x:Key="myButtonStyle" TargetType="{x:Type Button}">
            <Setter Property="Content">
                <!--<Setter.Value>X</Setter.Value>-->
                <Setter.Value><TextBlock Text="X" /></Setter.Value>
            </Setter>
            <Setter Property="Background">
                <Setter.Value>
                    <SolidColorBrush Color="Red" />
                </Setter.Value>
            </Setter>
        </Style>
    </Grid.Resources>
    <ItemsControl>
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="{x:Type DataObject}">
                <Button Height="24" Width="24" Style="{StaticResource myButtonStyle}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.Items>
            <DataObject />
            <DataObject />
            <DataObject />
        </ItemsControl.Items>
    </ItemsControl>
</Grid>

Solution:

Unfortunately, I still cannot explain why the 'Content' Setter fails to work on all but the last DataObject when the Content is set to be a control such as a TextBlock rather than straight text.

However, Dmitry's suggestion of using setting the 'ContentTemplate' instead of 'Content' is a very acceptable workaround that still allows for a re-usable Style.

<Grid>
    <Grid.Resources>
        <DataTemplate x:Key="textBlockWithX">
            <TextBlock Text="X" />
        </DataTemplate>
        <Style x:Key="myButtonStyle" TargetType="{x:Type Button}">
            <Setter Property="ContentTemplate" Value="{StaticResource textBlockWithX}" />
        </Style>
    </Grid.Resources>
    <ItemsControl>
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="{x:Type DataObject}">
                <Button Height="24" Width="24" Style="{StaticResource myButtonStyle}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.Items>
            <DataObject />
            <DataObject />
            <DataObject />
        </ItemsControl.Items>
    </ItemsControl>
</Grid>

Answer

H.B. picture H.B. · Jan 2, 2012

The answer to this is rather simple actually, every visual can only be the child of one object, unlike text like "X" which is just data.

If you create a style like this:

<Style>
    <Setter Property="Content">
        <Setter.Value>
             <TextBlock Text="X"/>
        </Setter.Value>
    </Setter>
<Style>

Only one TextBlock is created for all instances on which the style is applied, so the TextBlock will "jump" on every application and end up at the last item.

If you set the ContentTemplate however you create as the name implies a template which is used to generate the content independenctly for every object so you end up with one instance per control where the style applies.