Blog

Styles In Silverlight: Manipulating At Runtime

This article is part 4 of 4 in a series covering everything that I think is worth knowing about styles in Silverlight. The previous parts of the series are Styles in Silverlight: an Introduction and Styles in Silverlight - Inheritance, Precedence and Other Advanced Topics and Styles in Silverlight - Further Advanced Topics.

This part of the series explores manipulating, merging and modifying Styles at runtime. Manipulation of Styles runtime is quite an uncommon scenario and is not really documented, this article tries to cover all important aspects of it.

Modifying Styles Runtime: the IsSealed Property

Modifying the Setters of a Style can only be done if the IsSealed property is set to false. This property is false when the Style is created and is set to true once the Style is applied to any element in the visual tree. Thus it's safe to say that Styles are a immutable in a special way: they can only be modified until applied to any element in a visual tree.

Trying to modify a Style that is sealed (IsSealed property set to true) can result in multiple errors:

  • If trying to modify the value of a Setter in the Style, an UnauthorizedAccessException to be thrown with the error message Attempted to perform an unauthorized operation.
  • If trying to add another Setter to the Style, an Exception will be thrown with the (not so informative) error message Error HRESULT E_FAIL has been returned from a call to a COM component.
  • When removing a Setter from the Style's Setters, no exception is thrown, but the operation will have no result.

 

I've created a simple example to demonstrate how modifying the assigned Style object will not work and throw exceptions or not do anything:

The code behind for the event handlers on the buttons are as follows:

private void ChangeSetterValue_Click(object sender, RoutedEventArgs e)
{
    Message.Text = "";
    try
    {
        (MyEllipse.Style.Setters[0] as Setter).Value = 20;
    }
    catch (UnauthorizedAccessException ex)
    {
        // UnauthorizedAccessException is thrown with the message "Attempted to perform an unauthorized operation"
        Message.Text = ex.GetType().Name + ": " + ex.Message;
    }
}

private void AddStyleSetter_Click(object sender, RoutedEventArgs e)
{
    Message.Text = "";
    try
    {
        MyEllipse.Style.Setters.Add(new Setter(FrameworkElement.OpacityProperty, 0.9));
    }
    catch (Exception ex)
    {
        // Exception is thrown with the message "Error HRESULT E_FAIL has been returned from a call to a COM component"
        Message.Text = ex.GetType().Name + ": " + ex.Message;
    }
}

private void RemoveStyleSetter_Click(object sender, RoutedEventArgs e)
{
    Message.Text = "";
    // Nothing will happen when removing a setter from the Style's Setter
    MyEllipse.Style.Setters.Remove(MyEllipse.Style.Setters[1]);
}

The source of this application can be downloaded here: Changing Styles on the Fly.zip

Cloning Styles

As shown in the previous paragraph, since Styles can't be modified once the IsSealed property is set. There are scenarios when it would be desirable to (re-)use these Styles. To do so, we have to work around the sealed nature of Styles - the the most convenient way doing this is to clone a Style from the existing one and do modifications on that Style.

Thanks to extension methods introduced in .NET 3.5, creating a (deep) clone for Style is very easy:

public static class Extensions
{
    public static Style Clone(this Style style)
    {
        if (style == null)
            return null;
        Style clonedStyle = new Style(style.TargetType);
        clonedStyle.BasedOn = style.BasedOn;
        foreach (Setter setterToCopy in style.Setters)
        {
            clonedStyle.Setters.Add(new Setter()
            {
                Property = setterToCopy.Property,
                Value = setterToCopy.Value
            });
        }
        return clonedStyle;
    }
}

After cloning a Style, a new Style object is created. This object is not sealed (the IsSealed property is false), thus it's possible to modify it until it's applied to an element in the visual tree.

On an example let's see how we could clone an existing style, add and remove some of its Setters and re-apply the style to another element. Let's draw two ellipses, the top one being green, the bottom being red:

<userControl.Resources>
    <style TargetType="Ellipse" x:Name="TopEllipseStyle">
        <setter Property="Fill" Value="Green"/>
        <setter Property="Stroke" Value="Red"/>
        <setter Property="StrokeThickness" Value="1"/>
    </style>
    <style TargetType="Ellipse" x:Name="BottomEllipseStyle">
        <setter Property="Fill" Value="Blue"/>
    </style>
</userControl.Resources>
<stackPanel x:Name="LayoutRoot" Background="White" Orientation="Vertical">
    <ellipse x:Name="TopEllipse" Style="{StaticResource TopEllipseStyle}" Width="30" Height="30" Margin="0,5,0,5"/>
    <ellipse x:Name="BottomEllipse" Style="{StaticResource BottomEllipseStyle}" Width="30" Height="30" Margin="0,5,0,5"/>
    <button x:Name="BtnUseTopStyle" Content="Use top ellipse's style on bottom ellipse" Click="BtnUseTopStyle_Click" Width="350" Height="25"/>
</stackPanel>

On the click of the button let's assign a slightly modified version of the top ellipse's style to the bottom ellipse (using the previously defined Clone extension method on Style):

private void BtnUseTopStyle_Click(object sender, RoutedEventArgs e)
{
    // Create a new style using the style of the top ellipse
    var newStyle = TopEllipse.Style.Clone();
    // Change stroke thickness to 3
    newStyle.Setters.Add(new Setter(Ellipse.StrokeThicknessProperty, 3));
    // Remove the fill Setter
    newStyle.Setters.RemoveAt(0);
    BottomEllipse.Style = newStyle;
}

The result can be tested below, the new, modified Style (with a red stroke, 3 stroke thickness and no fill) is indeed applied to the bottom element when pressing the button:

Download the source for this example here: Cloning Styles.zip

Merging Styles Runtime

A more advanced scenario from simply modifying a few setters in a Style is merging two Styles into one. As discussed before, since a Style that has been applied to a visual element, can't be modified any more, we'll need to create a new Style object that merges the given styles.

There are two ways - an easier and a more complicated - way to go about this issue.

Using the BasedOn Property

In a previous part of the series I've covered how the BasedOn property implements Style inheritance.

An easy way of implementing merging of styles is to use this property when creating the merged style. If we have a ChildStyle and a ParentStyle property, a solution would be to set the BasedOn property of ChildStyle to ParentStyle. Of course because of how styles applied to visual elements can't be changed any more, this needs to be done on their clones. Thus a possible implementation of this merge using an extension method would be the following:

public static Style MergeByUsingBasedOnProperty(this Style childStyle, Style parentStyle)
{
    if (childStyle == null)
    {
        return parentStyle;
    }
    else if (parentStyle == null)
    {
        return childStyle;
    }
    else
    {
        Style style = childStyle.Clone();
        style.BasedOn = parentStyle;
        return style;
    }
}

Copying Setters of the Styles

Another, slightly more complicated approach is to create a Style by manually copying over the Setters from the ChildStyle and then copy the non conflicting Setters from the ParentStyle:

public static Style MergeByCopyingSetters(this Style childStyle, Style parentStyle)
{
    if (childStyle == null)
    {
        return parentStyle;
    }
    else if (parentStyle == null)
    {
        return childStyle;
    }
    else
    {
        // Clone style
        Style style = childStyle.Clone();
        foreach (Setter setter in parentStyle.Setters)
        {
            if (style.Setters.OfType().Where(x => x.Property.Equals(setter.Property)).Count() == 0)
            {
                style.Setters.Add(new Setter(setter.Property, setter.Value));
            }
        }
        return style;
    }
}

This approach can be beneficial if one is copying styles quite often and by using the first approach, the list of styles linked through the BasedOn property would grow indefinitely.

Note that the current implementation does not copy any setters from the BasedOn style of ParentStyle - if your application has this specific need, you'll have to add that in for yourself.

An Example of Both Approaches

The following example demonstrates how to use both approaches and how they lead to the same result. Click on either of the "Merge Styles" button to have the top and the bottom Ellipse's style merged. This merged style is expected to have a blue fill and an orange stroke:

The important parts for this example is as follows:

<UserControl.Resources>
    <Style TargetType="Ellipse" x:Key="Ellipse1Style">
        <Setter Property="Fill" Value="Blue"/>
    </Style>
    <Style TargetType="Ellipse" x:Key="Ellipse2Style">
        <Setter Property="StrokeThickness" Value="3"/>
        <Setter Property="Stroke" Value="Orange"/>
        <Setter Property="Fill" Value="Green"/>
    </Style>
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Orientation="Vertical">
    <Ellipse Width="30" Height="30" x:Name="Ellipse1" Style="{StaticResource Ellipse1Style}"/>
    <Ellipse Width="30" Height="30" x:Name="Ellipse2" Style="{StaticResource Ellipse2Style}"/>
    <StackPanel Orientation="Horizontal">
        <Ellipse Width="30" Height="30" x:Name="Ellipse3"/>
        <Button x:Name="BtnEllipse3" Content="Merge Styles using BasedOn property" Click="BtnEllipse3_Click"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
        <Ellipse Width="30" Height="30" x:Name="Ellipse4"/>
        <Button x:Name="BtnEllipse4" Content="Merge Styles by copying Setters" Click="BtnEllipse4_Click"/>
    </StackPanel>
</StackPanel> 
private void BtnEllipse3_Click(object sender, RoutedEventArgs e)
{
    Ellipse3.Style = Ellipse1.Style.MergeByUsingBasedOnProperty(Ellipse2.Style);
}

private void BtnEllipse4_Click(object sender, RoutedEventArgs e)
{
    Ellipse4.Style = Ellipse1.Style.MergeByCopyingSetters(Ellipse2.Style);
}

Download the full source of this example here: Merging Styles.zip

Some Bugs when Manipulating Styles at Runtime

There are some smaller bugs I've come across when manipulating Styles runtime in Silverlight. These are the following:

  • StrokeDashArray problems - if the Style contains a Setter for StrokeDashArray, a strange behaviour occurs due to a bug in Silverlight regarding StrokeDashArray Setters. Thus when StrokeDashArray Setters are present in a Style, when programmatically manipulating this style in Silverlight, this Setter needs special handling due to this bug.
  • Disappearing Style Setters in Silverlight 3 and WP7 - the issue is not present in SL4 or WPF, but in SL3 and on WP7 in some special cases the Setters of a Style are unexpectedly set to null. See my article on these disappearing Style Setters for more detailed explanation of the bug

Conclusion

In this article I've shown ways to modify, clone and merge Styles runtime. In my experience using these operations on styles is very rare when developing in Silverlight and as such, not that well documented. I came across having to dive into these operations myself when developing Silverlight components - I think it's safe to say that for most of the LOB development this topic can almost always be avoided.

I hope you've found this article useful - this was the final article in the series working through Styles in Silverlight.

Interested in an easy to style, performant Silverlight / WPF charting library? Give the free version of Visiblox a try!
 

Comments

[...] Modifying Styles Runtime: the IsSealed Property – modifying the Setters of a Style can only be done if the IsSealed property is set to false and this property is set to true as soon as the style gets applied to an element in the visual tree. [...]

 

Post a comment