Partager via


Styles Are Forever – But ControlTemplates Are a Developer’s Best Friend

I’ve been working on a project where I have a Button with a default Style that I wanted to change when the Button was clicked.  So let’s say you have a Button and a couple of Styles as shown below:

 <UserControl x:Class="SilverlightApplication1.Page"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <UserControl.Resources>
        <Style x:Key="RedStyle" TargetType="Button">
            <Setter Property="FontFamily" Value="Verdana"/>
            <Setter Property="Foreground" Value="Red"/>
            <Setter Property="FontSize" Value="11"/>
        </Style>
        <Style x:Name="BlueStyle" TargetType="Button">
            <Setter Property="FontFamily" Value="Courier New"/>
            <Setter Property="Foreground" Value="Blue"/>
            <Setter Property="FontSize" Value="11"/>
        </Style>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <Button x:Name="MyButton" Content="Style Me" Style="{StaticResource RedStyle}"/>
    </Grid>
</UserControl>

I want the default style for this Button to be RedStyle so I have the Style set to:

 Style="{StaticResource RedStyle}"

Now, in the code, I want to change the Style when the UserControl is clicked:

 void MyButton_Click(object sender, RoutedEventArgs e)
{
    Style blueStyle = this.Resources["BlueStyle"] as Style;
    if (blueStyle != null)
    {
        this.MyButton.Style = blueStyle;
    }
}

But the problem comes along when you actually click the the control and the following exception is thrown:

Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED))

Wait, why can’t I change my style?  You might think that the reason is because assigning the Style as a StaticResource makes it permanent.  That’s true.  However, the whole truth is that once a Style is assigned to an object, it can never have another Style assigned to it.  Let’s test this out by not assigning the Style as a StaticResource in the XAML.  Instead, let’s assign it in the constructor:

 public Page()
{
    InitializeComponent();
    this.MyTextBlock.Style = this.Resources["RedStyle"] as Style;
    this.MouseLeftButtonUp += new MouseButtonEventHandler(Page_MouseLeftButtonUp);
}

Now when we click the control what happens?

Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED))

But I really want to change the Style of the Button when I click it.  Enter the ControlTemplate.  First, let’s change the default style to include a ControlTemplate:

 <Style x:Key="BaseStyle" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid x:Name="Content" Background="Black" Width="200" Height="100">
                    <Grid.Resources>
                        <Style x:Key="RedStyle" TargetType="TextBlock">
                            <Setter Property="FontFamily" Value="Verdana"/>
                            <Setter Property="Foreground" Value="Red"/>
                            <Setter Property="FontSize" Value="11"/>
                        </Style>
                    </Grid.Resources>
                    <TextBlock Text="{TemplateBinding Content}" Style="{StaticResource RedStyle}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Here I’m saying that I want my Button to be a Grid that contains a TextBlock.  It looks like this:

 RedStyle

Now I’m going to add another ControlTemplate that will turn my text blue and change the font:

 <ControlTemplate x:Key="BlueTemplate" TargetType="Button">
    <Grid x:Name="Content" Background="Yellow" Width="100" Height="50">
        <Grid.Resources>
            <Style x:Key="BlueStyle" TargetType="TextBlock">
                <Setter Property="FontFamily" Value="Courier New"/>
                <Setter Property="Foreground" Value="Blue"/>
                <Setter Property="FontSize" Value="13"/>
            </Style>
        </Grid.Resources>
        <TextBlock Text="{TemplateBinding Content}" Style="{StaticResource BlueStyle}"/>
    </Grid>
</ControlTemplate>

My MyButton_Click() method now changes to:

 void MyButton_Click(object sender, RoutedEventArgs e)
{
    ControlTemplate blueTemplate = this.Resources["BlueTemplate"] as ControlTemplate;
    if (blueTemplate != null)
    {
        this.MyButton.Template = blueTemplate;
    }
}

Now, when I click the Style Me button, it looks like this:

BlueStyle

The reason this now works is that while Style is write-once, the Template property isn’t.  It can be set any number of times.

Comments