Blog

Styles In Silverlight: Further Advanced Topics

This article is part 3 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.

This part of the series explores some further advanced topics: data binding, change notification, declaring styles in code behind and some other useful notes on styles.

Style is a Dependency Property

The Style property is a dependency property. This means that they give you some extra features that CLR properties would not. The features that can be used in case of the Style dependency property are the following:

  • Data binding
  • Property changed notifications
  • ClearValue

Note that dependency properties have two other important features that can't be used with the Style dependency property: animation and styles:

  • The Style property can't be animated as it's value type isn't supported by any Timeline derived animation type.
  • Styles can't be applied to the Style dependency property... because this is the property that styles need to be applied through (all dependency properties can be styled except for Style).

Data binding

As Style is a dependency property it can be data binded - even though there seem to be very few real world examples when this functionality would actually be needed.

The following example demonstrates how this feature works. Two Ellipses are used: LeftEllipse and RightEllipse. RightEllipse binds its style to LeftEllipse so whenever the style of LeftEllipse changes, so does the style of RightEllipse. When clicking on the button it changes the style of LeftEllipse and thus through the binding the style of RightEllipse is changed as well:

The code (XAML):

<userControl.Resources>
    <style x:Key="BlueEllipseStyle" TargetType="Ellipse">
        <setter Property="Fill" Value="Blue"/>
        <setter Property="Stroke" Value="LightBlue"/>
        <setter Property="StrokeThickness" Value="4"/>
    </style>
    <style x:Key="RedEllipseStyle" TargetType="Ellipse">
        <setter Property="Fill" Value="Red"/>
        <setter Property="Stroke" Value="LightRed"/>
        <setter Property="StrokeThickness" Value="4"/>
    </style>
    <style x:Key="GreenEllipseStyle" TargetType="Ellipse">
        <setter Property="Fill" Value="Green"/>
        <setter Property="Stroke" Value="LightGreen"/>
        <setter Property="StrokeThickness" Value="4"/>
    </style>
</userControl.Resources>
<stackPanel Orientation="Vertical">
    <stackPanel Orientation="Horizontal">
        <ellipse x:Name="LeftEllipse" Width="40" Height="40" Style="{StaticResource GreenEllipseStyle}"/>
        <ellipse x:Name="RightEllipse" Stroke="Orange" Width="40" Height="40" Style="{Binding ElementName=LeftEllipse, Path=Style}" Margin="5,0,0,0"/>
    </stackPanel>
    <button x:Name="ButtonChangeLeftEllipseStyle" Content="Change the style of the left Ellipse" Click="ButtonChangeLeftEllipseStyle_Click" Width="200" HorizontalAlignment="Left" Margin="0,5,0,0"/>
</stackPanel>

The code behind:

public MainPage()
{
    InitializeComponent();
}

private void ButtonChangeLeftEllipseStyle_Click(object sender, RoutedEventArgs e)
{
    // Iterate through the three styles defined in UserControl.Resources on each click
    switch (_counter % 3)
    {
        case 0:
            LeftEllipse.Style = Resources["BlueEllipseStyle"] as Style;
            break;
        case 1:
            LeftEllipse.Style = Resources["RedEllipseStyle"] as Style;
            break;
        case 2:
            LeftEllipse.Style = Resources["GreenEllipseStyle"] as Style;
            break;
    }
    _counter++;
}

Note that in the example the LineStroke property of RightEllipse is set locally. Because of the style precedence the local value has a higher precedence than the one set via Style, thus the LineStroke of the right ellipse always remains orange.

Property changed notifications

Unfortunately in Silverlight (as of version 4) there is no way to directly register for property change notifications for the dependency properties declared in the framework. In WPF this is possible via obtaining a descriptor for the dependency property and adding a change notification listener. Silverlight at the moment does not offer this functionality (change notifications can easily be received for custom defined dependency properties though).

However there is a workaround for this problem by creating an attached property ,setting a binding to the Style property for this new attached property and subscribing for the change notification of the attached property. This is quite a complicated and not really nice solution, but up to now the only workaround I've found for subscribing to Style change notifications. See the following simple example on how this works:

The XAML code:

<userControl.Resources>
    <style x:Key="BlueEllipseStyle" TargetType="Ellipse">
        <setter Property="Fill" Value="Blue"/>
    </style>
    <style x:Key="RedEllipseStyle" TargetType="Ellipse">
        <setter Property="Fill" Value="Red"/>
    </style>
</userControl.Resources>
<stackPanel Orientation="Vertical">
    <stackPanel Orientation="Horizontal">
        <ellipse x:Name="MyEllipse" Width="30" Height="30" Style="{StaticResource RedEllipseStyle}"/>
    </stackPanel>
    <textBlock x:Name="TbxMessage" Margin="0,5,0,0"/>
    <button x:Name="ButtonChangeEllipseStyle" Content="Change the style of the Ellipse" Click="ButtonChangeEllipseStyle_Click" Width="200" HorizontalAlignment="Left" Margin="0,5,0,0"/>
</stackPanel>

And the code behind. Creating the attached property, binding it to the Style property of the Ellipse and subscribing for a change callback is all done in the RegisterStyleChangedNotification() method:

public MainPage()
{
    InitializeComponent();
    RegisterStyleChangedNotification();
    _tbxBoxMessage = TbxMessage;
}

private void RegisterStyleChangedNotification()
{
    // Create a binding for the Style property on the MyEllipse element
    string propertyName = "Style";
    FrameworkElement element = MyEllipse;
    Binding binding = new Binding(propertyName) { Source = element };

    // Create an attached property with the ListenAttached_Style name
    // The OnStylePropertyChanged handler will be called when this property has changed
    var prop = System.Windows.DependencyProperty.RegisterAttached(
        "ListenAttached" + propertyName,
        typeof(object),
        typeof(UserControl),
        new System.Windows.PropertyMetadata(OnStylePropertyChanged));

    // Bind the newly created attached property to the Style
    element.SetBinding(prop, binding);
}

private static void OnStylePropertyChanged(DependencyObject d,  DependencyPropertyChangedEventArgs e)
{
    Ellipse myClass = d as Ellipse;
    if (e.OldValue != null)
    {
        _tbxBoxMessage.Text = "The style of the ellipse has been changed!";
    }
}

private void ButtonChangeEllipseStyle_Click(object sender, RoutedEventArgs e)
{
    MyEllipse.Style = Resources["BlueEllipseStyle"] as Style;
}

The source code for this example can be downloaded here: Style Property Changed Notification.zip

Note how simple it would be to do the same thing in WPF:

DependencyPropertyDescriptor desc = DependencyPropertyDescriptor.FromProperty(FrameworkElement.StyleProperty, typeof(FrameworkElement));
            desc.AddValueChanged(this, (s,e)=>this.OnStylePropertyChanged(new DependencyPropertyChangedEventArgs()));

ClearValue

Dependency properties support a special operation: ClearValue. This sets their value to an unset state, to the point when no value is defined. This feature is very useful when we want to reset the Style of an object.

One would think that clearing the value of a dependency property is the same as setting it to null. However, this is not the case as setting it to null would break the style precedence.

For a simple example between the difference of using ClearValue and setting the property to null, see the following example. An ellipse is displayed with a green fill that's set via a style. At the same time an implicit style is defined for ellipses, which of course only gets applied if the Style of the ellipse isn't set. When setting the Style of the ellipse to null, the ellipse disappears as it won't have a Fill set. However when using ClearValue() the Style property will become unset and according to the style precedence the implicit style will be applied:

The XAML and code behind for this example is as follows:

<userControl.Resources>
    <style TargetType="Ellipse">
        <setter Property="Fill" Value="Blue"/>
    </style>
    <style TargetType="Ellipse" x:Key="GreenEllipseStyle">
        <setter Property="Fill" Value="Green"/>
    </style>
</userControl.Resources>
<stackPanel Orientation="Vertical">
    <ellipse x:Name="MyEllipse" Width="30" Height="30" Style="{StaticResource GreenEllipseStyle}"/>
    <button x:Name="SetStyleToNull" Click="SetStyleToNull_Click" Width="150" Height="25" Margin="0,5,0,0">Set style to null</button>
    <button x:Name="ClearStyle" Click="ClearStyle_Click" Width="150" Height="25" Margin="0,5,0,0">Clear style value</button>
</stackPanel>
private void SetStyleToNull_Click(object sender, RoutedEventArgs e)
{
    MyEllipse.Style = null;
}

private void ClearStyle_Click(object sender, RoutedEventArgs e)
{
    MyEllipse.ClearValue(Ellipse.StyleProperty);
}

Download the source code for this sample here: Style - ClearValue.zip

Declaring Styles In Code Behind

As I've mentioned in the first part of the series, styles can either be declared in XAML or in the code behind (this, of course is true for almost everything in Silverlight - apart from a few special cases where definitions can only be made in code behind).

Declaring a Style programmatically consists of the following steps:

  • Create a Style object
  • Specify the TargetType
  • Add the Setters and set the Property and Value for each setter

So let's see how we could declare the equivalent of the following style:

<style TargetType="Ellipse">
    <setter Property="Fill" Value="Green"/>
    <setter Property="Stroke" Value="Blue"/>
    <setter Property="StrokeThickness" Value="4"/>
</style>

The following code would create this same style:

// Create the Style object and set it's TargetType
var style = new Style();
style.TargetType = typeof(Ellipse);

// Add the Setters to the Style
var fillColor = new SolidColorBrush(){Color=Colors.Green};
var strokeColor = new SolidColorBrush() { Color = Colors.Blue };
style.Setters.Add(new Setter(){Property =Ellipse.FillProperty,Value=fillColor});
style.Setters.Add(new Setter() { Property = Ellipse.StrokeProperty, Value = strokeColor });
style.Setters.Add(new Setter() { Property = Ellipse.StrokeThicknessProperty, Value = 4 });

// Assign the Style to an element
MyEllipse.Style = style;

There are a few things to note on creating styles in code behind:

  • Note how in the XAML for the first setter's property we've defined Fill, in the code behind however we've used the Ellipse.FillProperty static property. This is because the XAML parser does this conversion and makes creation of the style more intuitive. When working in the code behind it's useful to understand how dependency properties actually work.
  • Also note how in XAML we could just set "Green" for the Fill property, in code behind however we've had to create a SolidColorBrush instance. This is again due to the XAML parser helping out users. (For more details on how this functionality can be replicated in WPF, see Colin's article)

Summing it up, even though styles can indeed can be declared in code behind, they are far more complicated and tedious to do so this way. My advice would be to avoid declaring them this way - unless needed for various reasons, e.g. constructing them dynamically in runtime. (The last part of the series will be focusing on this topic).

Download example code that uses styles declared in code behind here: Declaring Styles In Code Behind.zip

Other Notes About Styles

Only Dependency Properties Can Be Styled

In the previous paragraph we've seen what code Style declarations in XAML translate to. As a result of the way style setters work - their property refers to a dependency property - only dependency properties can be styled, CLR properties cannot.

Trying to assign a CLR property to a Setter of a Style will result in a compile time error. If doing the same thing in XAML, however, the project will compile, but the following error will be thrown runtime: "The property 'MyCLRProperty' was not found in type 'MyClass'.

Not All Classes Have Styles: Only Ones Derived From FrameworkElement

In Silverlight not all elements have a Style property, only the ones that inherit from FrameworkElement. Practically this isn't too relevant when working with built-in Silverlight objects as all of the classes one would expect to be stylable (e.g. Button, Checkbox, Ellipse and other visual elements) are all subclasses of FrameworkElement.

However when creating a custom class, if planning to make it stylable, make sure to inherit FrameworkElement. (If creating a UserControl or a Custom Control, these classes already inherit from FrameworkElement.)

Conclusion

In this part of the series I've covered advanced topics related to Styles and Dependency Properties. This included the following:

  • Style is a dependency property. This means that it supports the following features:
    • Data binding: it's possible to bind styles on to another - even though this is probably a very rare real world scenario.
    • Property changed notifications - it's not possible to directly subscribe to property changed notifications for the Style property due to the feature missing in Silverlight, however there is a complicated, but working workaround available to detect when the Style is being changed.
    • ClearValue - unsetting the value of a Style is not the same as setting it to null. When wanting to reset or clear the style, ClearValue is the method to be used.
  • Declaring Styles in code behind is possible, it results in more complex code though then doing the same thing in XAML.
  • Only dependency properties can be styled. Styling CLR properties will result in either a compile time or runtime error.
  • Only classes inheriting from FrameworkElement have the Style property and thus can be styled. All of the built-in visual controls, the Control and UserControl class are descendants of this class. It's worth remembering this when creating custom classes that might need to be styled.

I hope this part of the series was useful. In the final part of the series I'll be exploring how to manipulate, clone and merge Styles in runtime.

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

Post a comment