13

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>
H.B.
  • 166,899
  • 29
  • 327
  • 400
Scott
  • 11,840
  • 6
  • 47
  • 54
  • Hi, you obviously know that the culprit is Style within a DataTemplate, once you use Content as a plain property it all starts working. –  Mar 02 '11 at 17:07
  • There is definately a workaround in this contrived/simplified example. But I'm not sure I understand why this is 'By Design'. Setting the Content Setter's Value to "X" works... but setting it to a TextBlock only works for the last item... seems strange to me. – Scott Mar 02 '11 at 17:20

2 Answers2

12

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.

H.B.
  • 166,899
  • 29
  • 327
  • 400
7

Here's a working sample:

<Window x:Class="Styles.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Styles"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.Resources>
            <Style x:Key="A" TargetType="{x:Type Button}">
                <Style.Setters>
                    <Setter Property="Content" Value="X"></Setter>
                </Style.Setters>
            </Style>
        </Grid.Resources>
        <ItemsControl>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Height="24" Width="24" Style="{StaticResource A}">

                    </Button>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.Items>
                <DataObject></DataObject>
                <DataObject></DataObject>
                <DataObject></DataObject>
            </ItemsControl.Items>
        </ItemsControl>
    </Grid>

</Window>

Edit1 Doh.. Got it working, the trick is to use ContentTemplate.

<Window x:Class="Styles.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Styles"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="A">
                <TextBlock>X</TextBlock>
            </DataTemplate>
        </Grid.Resources>
        <ItemsControl>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Height="24" Width="24" ContentTemplate="{StaticResource A}">

                    </Button>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.Items>
                <DataObject></DataObject>
                <DataObject></DataObject>
                <DataObject></DataObject>
            </ItemsControl.Items>
        </ItemsControl>
    </Grid>

</Window>

Edit2: A sample of more complex ContentTemplate:

<Window x:Class="Styles.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Styles"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="A">
                <StackPanel Width="30" Orientation="Horizontal">
                    <Grid Background="White" Width="10" Height="10"></Grid>
                    <Grid Background="Blue" Width="10" Height="10"></Grid>
                    <Grid Background="Red" Width="10" Height="10"></Grid>
                </StackPanel>
            </DataTemplate>
        </Grid.Resources>
        <ItemsControl>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Height="24" Width="34" ContentTemplate="{StaticResource A}">

                    </Button>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.Items>
                <DataObject></DataObject>
                <DataObject></DataObject>
                <DataObject></DataObject>
            </ItemsControl.Items>
        </ItemsControl>
    </Grid>

</Window>
  • I don't think I'm using an implicit Style however. I don't even have a Resources Section in example code. I'm Explicitly setting the Style. – Scott Mar 02 '11 at 17:16
  • Hi, you actually are, –  Mar 02 '11 at 17:20
  • I'm explictly Setting , this is not in a Resources section. I can take out TargetType="{x:Type Button}", and change my Setter to be Property="Button.Content", and I still get the same results. (I only use the TargetType to get intellisense). – Scott Mar 02 '11 at 17:26
  • Scott is considered an implicit construction in WPF as you are not implying any specific **instance**, but rather a **group** of inastances, having Type = Button. –  Mar 02 '11 at 17:28
  • 1
    I've added a second example to my question to provide an example without implicit Styling. If my understanding is correct, having an x:Key in my Style cannot be considered an implicit style. I still see the issue without implicit styling. – Scott Mar 02 '11 at 17:38
  • Also, I am fairly certain my is not implicit. is just different way to write . My style is not being applied to multiple buttons because of implicit styling. It is being explicity applied to a single button within a DataTemplate. Then multiple instances of the DataTemplate are being created for each DataObject. I do appreciate you taking the time to help with this problem. But I don't think the link provided is the same issue that I am seeing. – Scott Mar 02 '11 at 17:54
  • The background (as well as most every other) property works! I probably should have shown an example of that in my original post. It appears to be specifically the 'Content' Property that fails (except for the last item for some reason). – Scott Mar 02 '11 at 19:38
  • I think I've got working by using ContentTemplate + Resource. –  Mar 02 '11 at 20:00
  • For now my fix is to set the whole template rather than using a style. Unfortunately, in my actual project with a much more complex button style (that is used in other places as well) I'll have to maintain the same code in two places. I'm going to keep this question open for a while to see if anybody else has any insight. But if nobody else has an explanation, I think I might submit this as a bug on the Microsoft Connect site. – Scott Mar 02 '11 at 20:01
  • I see your edited solution and I agree that works as an alternative to the contrived example. The problem is if you don't want a simple "X" for your Content, but you want a more complex set of other controls as your Content. – Scott Mar 02 '11 at 20:05
  • 1
    Sorry I thought that it's resolved now - you don't have to set the whole template as there seems to be a dedicated rproperty - ContentTemplate. I've updated my answer to include the actual solution.You can use create templates of any level of complexity. –  Mar 02 '11 at 20:06
  • Aha... ContentTemplate... this provides a very flexible solution. The Style can still be used now (instead of a Setter for Content, make one for ContentTemplate)... it applies to everything! I will do some research on ContentTemplate before deciding whether or not to submit a bug on Microsoft Connect, as the initial issue encountered still seems like a Bug. But at least there is a flexible alternative now!! – Scott Mar 02 '11 at 20:19
  • Do you mind if I modify your answer slightly before accepting? – Scott Mar 02 '11 at 20:20
  • Fire ahead, I've done it a few times with my answers, to make them clearer. –  Mar 02 '11 at 20:26
  • I guess I don't have edit privileges, so I'll just put my final solution in an edit of my original post. By you still got my +1 and marked answer. Thanks for the time and effort you put into this Dmitry! – Scott Mar 02 '11 at 20:54
  • I'm glad I was helpful, Scott. Cheers. –  Mar 02 '11 at 21:10