This article is part 2 of 4 in a series attempting to cover
everything I think is worth knowing about styles in Silverlight 4.
The previous article in the series was
Styles in Silverlight: an Introduction that covered what styles
are, how they can be defined and some of their limitations.
This article focuses on some more advanced topics that are handy
when building more complex applications: re-using the same styles
for different types, style inheritance, style precedence and style
setter precedence.
Re-Using Styles
for Different Types
As discussed in the previous article,
all Style elements need to specify a TargetTypethat specifies
the type of class they need to be applied to. If an application
uses several different types of visual elements, this can result in
having to specify different styles again and again. Let's say for
example our application has an Ellipse and a Rectangle and we want
to have both of them have a blue fill. The straightforward way of
doing this using Styles is declaring two different styles for each
of them and applying those using StaticResources in XAML:
<userControl.Resources>
<style TargetType="Ellipse" x:Key="CustomEllipseStyle">
<setter Property="Fill" Value="Blue"/>
</style>
<style TargetType="Rectangle" x:Key="CustomRectangleStyle">
<setter Property="Fill" Value="Blue"/>
</style>
</userControl.Resources>
<stackPanel Orientation="Vertical">
<ellipse Width="30" Height="30" Style="{StaticResource CustomEllipseStyle}"/>
<rectangle Width="30" Height="30" Style="{StaticResource CustomRectangleStyle}" Margin="0,10,0,0"/>
</stackPanel>
However in this particular case it's possible to combine these
styles as Ellipse and Rectangle inherit from the same parent class:
Shape. The Fill property that we are setting in the styles is
defined in Shape, so we can define a single style with the
TargetType set to Shape, then apply this Style to the elements:
<userControl.Resources>
<style TargetType="Shape" x:Key="CustomShapeStyle">
<setter Property="Fill" Value="Blue"/>
</style>
</userControl.Resources>
<stackPanel Orientation="Vertical">
<ellipse Width="30" Height="30" Style="{StaticResource CustomShapeStyle}"/>
<rectangle Width="30" Height="30" Style="{StaticResource CustomShapeStyle}" Margin="0,10,0,0"/>
</stackPanel>
So the TargetType of the style does not necessarily have to be
the type of the visual element itself. It can be a parent class of
the visual element if the setters within this style only refer to
properties of this parent class. The XAML code for the second
example is shorter, at the same time both examples look the same:
Download the source for these two examples: Using The Same
Style For Separate Classes.zip.
Style Inheritance:
The BasedOn property
The Style object defines the BasedOn property which makes it
easy to inherit styles. This property works just as expected: a
style that defines a BasedOn style inherits all of the setters from
this parent style. Only single inheritance is
supported, and the length of the inheritance chain is not
limited (so properties aren't just inherited from the
parent, but the grandparent, great-grandparent and so on if they're
set)
Let's see a simple example on how to use this property:
<userControl.Resources>
<style x:Key="BlueBorderStyle" TargetType="Rectangle">
<setter Property="Stroke" Value="Blue"/>
<setter Property="StrokeThickness" Value="3"/>
</style>
<style x:Key="GreenFillStyle" TargetType="Rectangle">
<setter Property="Fill" Value="Green"/>
</style>
<style x:Key="GreenFillStyleBasedOnBlueBorderStyle" TargetType="Rectangle" BasedOn="{StaticResource BlueBorderStyle}">
<setter Property="Fill" Value="Green"/>
</style>
<style x:Key="SemiTransparentStyleBasedOnGreenFillStyleBasedOnBlueBorderStyle" TargetType="Rectangle" BasedOn="{StaticResource GreenFillStyleBasedOnBlueBorderStyle}">
<setter Property="Opacity" Value="0.5"/>
</style>
</userControl.Resources>
<stackPanel Orientation="Vertical">
<rectangle Width="100" Height="50" Style="{StaticResource BlueBorderStyle}" x:Name="Rectangle1"/>
<rectangle Width="100" Height="50" Style="{StaticResource GreenFillStyle}" Margin="0,5,0,0" x:Name="Rectangle2"/>
<rectangle Width="100" Height="50" Style="{StaticResource GreenFillStyleBasedOnBlueBorderStyle}" Margin="0,5,0,0" x:Name="Rectangle3"/>
<rectangle Width="100" Height="50" Style="{StaticResource SemiTransparentStyleBasedOnGreenFillStyleBasedOnBlueBorderStyle}" Margin="0,5,0,0" x:Name="Rectangle4"/>
</stackPanel>
In the example the first rectangle uses a style with a blue
border (BlueBorderStyle). The second one uses a style with a green
fill (GreenFillStyle). The third one uses a Style that defines a
green fill and inherits BlueBorderStyle. The fourth one uses a
Style that defines 0.5 opacity and inherits the previous style.
Download the source of this example here: Styles: the BasedOn
Property.zip
Implicit
Styling
Up to Silverlight 4 when an application had multiple instances
of a visual element that needed to be styled the same way, styles
had to be assigned to the individually. This isn't a big deal,
however it would have been nice having a way to define the default
styles for particular elements.
Let's look at a simple example of an application consisting of 4
buttons. By default all buttons should have text rendered in red on
them. At the same time we want the right button to display blue
text. The straightforward way to do this is the following:
<userControl.Resources>
<style x:Key="DefaultButtonStyle" TargetType="Button">
<setter Property="Foreground" Value="Red"/>
<setter Property="Width" Value="150"/>
<setter Property="Height" Value="30"/>
<setter Property="Margin" Value="5,0,0,0"/>
</style>
<style x:Key="CustomButtonStyle" TargetType="Button">
<setter Property="Foreground" Value="Blue"/>
<setter Property="Width" Value="150"/>
<setter Property="Height" Value="30"/>
<setter Property="Margin" Value="5,0,0,0"/>
</style>
</userControl.Resources>
<stackPanel Orientation="Horizontal">
<button Content="Button 1" Style="{StaticResource DefaultButtonStyle}"/>
<button Content="Button 2" Style="{StaticResource DefaultButtonStyle}"/>
<button Content="Button 3" Style="{StaticResource DefaultButtonStyle}"/>
<button Content="Custom Button" Style="{StaticResource CustomButtonStyle}"/>
</stackPanel>
However, defining this kind of "default style" has been made
possible since Silverlight 4 introduced implicit
styles. Implicit styles need to be declared in the
Resources part of the XAML the same way as regular styles do,
except the x:Key attribute should not be declared on them. So the
code for the same example using implicit styles is the
following:
<userControl.Resources>
<!-- If no x:Key property is declared, the style will be applied as implicit-->
<style TargetType="Button">
<setter Property="Foreground" Value="Red"/>
<setter Property="Width" Value="150"/>
<setter Property="Height" Value="30"/>
<setter Property="Margin" Value="5,0,0,0"/>
</style>
<!-- Since the x:Key property is defined on this style, it will not be treated as implicit-->
<style x:Key="CustomButtonStyle" TargetType="Button">
<setter Property="Foreground" Value="Blue"/>
<setter Property="Width" Value="150"/>
<setter Property="Height" Value="30"/>
<setter Property="Margin" Value="5,0,0,0"/>
</style>
</userControl.Resources>
<stackPanel Orientation="Horizontal">
<button Content="Button 1" />
<button Content="Button 2" />
<button Content="Button 3" />
<button Content="Custom Button" Style="{StaticResource CustomButtonStyle}"/>
</stackPanel>
Both examples look the same:
In the above example implicit styles didn't seem that much of a
gain. However implicit styles help a lot by removing the need to
bind default styles to all elements in an application - the larger
the application, the more sense it makes to use this way of
declaring default styles.
Download the source code for this example here: Implicit Styling.zip
Note that implicit styles may not always work when using
templates with visual elements that are not sublclasses of Control
(e.g. using a TextBlock in a DataTemplate will ignore the implicit
style). For a little more detail on this see
Mohamed Mosallem's blog article on the subject.
Style
Precedence
Only dependency properties can be styled (more on this in the
next part of the series), therefore Styles have their place in the
dependency value precedence
This value precedence is as follows (through the example using
the Foreground property of a Button):
- Animations have the highest precedence. That
is if the Foreground property of the Button is animated (e.g.
changing the foreground color from blue to yellow using a ColorAnimation), the value that the animation
sets will be applied.
- Locally set valuesare next in the list. If no
animation is applied to e.g. the Foreground of a Button and the
value is set by locally assigning, then this value will be applied.
Locally assigning happens the following way:
<button Foreground="Blue"/>
- Templated properties: If none above are set,
then values set in the control templates are applied. This series
doesn't focus on templating, for more information on this see Scott
Guthrie's post on templating.
- Style setters: if none of the above are set,
only then do the setters defined in the style get applied (if no
style is defined then the framework will look for a suitable
implicit style and use that if defined)
<userControl.Resources>
<style x:Key="CustomButtonStyle" TargetType="Button">
<setter Property="Foreground" Value="Blue"/>
</style>
</userControl.Resources>
<button Style="{StaticResource CustomButtonStyle}" Content="Push me!"/>
- Default value: dependency properties can have
default values defined. If none of the above are specified, then
this default value gets applied.
The most important thing in this list is when working with
styles is that a style setter is only applied to the element
if:
- No animation is animating the given property
- The given property is not set locally
- The given property is not hard coded to a specific value in the
control template (if the element has a control template)
Style Setter Precedence
When listing the Setters within the style, it is allowed to
declare multiple setters for the same property. In this case the
last Setter will be applied to the element.
See the following example that demonstrates this behaviour:
<userControl.Resources>
<style x:Key="CustomButtonStyle" TargetType="Button">
<setter Property="Foreground" Value="Red"/>
<setter Property="Foreground" Value="Yellow"/>
<setter Property="Foreground" Value="Green"/>
</style>
</userControl.Resources>
<grid>
<button Width="150" Height="30" Content="Button 1" Style="{StaticResource CustomButtonStyle}"/>
</grid>
In the example multiple setters are declared for the Foreground
property of a Button. Of these the last one - Green - gets applied
indeed. Download the source for this example here: Style Setter
Precedence.zip
Conclusion
In this part of the series I've covered some cases of more
advanced usage of styles. These cases covered:
- Re-Using the styles for different types: the
TargetType of the Style can be a parent type of the target object
as long as it only sets properties of the parent type
- Inheriting Styles can be done using the
BasedOn property. Styles support single inheritance and the depth
of inheritance is not limited.
- Implicit styling: in Silverlight 4 the default
styles of elements on a page / control can be specified in a simple
way using implicit styles
- Style precedence: a Style's Setters only get
applied if the object's particular property is not animated, not
set locally and not set in the control template belonging to the
object
- Style Setters' precedence: setters specifying
the value of the same property may be declared within the same
Style. The last one of them has the highest precedence and will be
used.
I hope you found this part interesting. In the next part of the
series I'll be covering further advanced topics such as declaring
and using styles in code behind, data binding to styles and getting
style change notifications. Stay tuned!