Styles are most often declared in the various Resources dictionaries of the application, along with various templates, application colors, and brushes. The Resources property is of type ResourceDictionary and declared in the FrameworkElement class and so virtually all UI elements inherit it and can therefore host our styles and other resources.
Although the Resources property is of type ResourceDictionary, we do not need to explicitly declare this element:
<Application.Resources> <ResourceDictionary> <!-- Add resources here --> </ResourceDictionary> </Application.Resources>
While there are some occasions when we do need to explicitly declare the ResourceDictionary, it will be implicitly declared for us if we do not:
<Application.Resources> <!-- Add Resources here --> </Application.Resources>
Every resource in each collection must have a key that uniquely identifies them. We use the x:Key directive to explicitly set this key, however, it can also be set implicitly as well. When we declare styles in any Resources section, we can specify the TargetType value alone, without setting the x:Key directive, in which case the style will be implicitly applied to all elements of the correct type that are in the scope of the style:
<Resources> <Style TargetType="{x:Type Button}"> <Setter Property="Foreground" Value="Green" /> <Setter Property="Background" Value="White" /> </Style> </Resources>
In this case, the value for the x:Key directive is implicitly set to {x:Type Button}. Alternatively, we can set the x:Key directive explicitly, so that the style must also be applied explicitly:
<Resources> <Style x:Key="ButtonStyle"> <Setter Property="Button.Foreground" Value="Green" /> <Setter Property="Button.Background" Value="White" /> </Style> </Resources> ... <Button Style="{StaticResource ButtonStyle}" Content="Go" />
Styles can have both values set as well, as shown in the following code:
<Resources> <Style x:Key="ButtonStyle" TargetType="{x:Type Button}"> <Setter Property="Foreground" Value="Green" /> <Setter Property="Background" Value="White" /> </Style> </Resources>
But a compilation error will be thrown if neither value is set:
<Resources> <Style> <Setter Property="Foreground" Value="Green" /> <Setter Property="Background" Value="White" /> </Style> </Resources>
The preceding XAML would result in the following compilation error:
The member "Foreground" is not recognized or is not accessible.
The member "Background" is not recognized or is not accessible.
When a StaticResource with a specific key is requested, the lookup process first looks in the local control; if it has a style and that style has a resource dictionary, it checks that first; if there is no item with a matching key, it next looks in the resource collection of the control itself.
If there is still no match, the lookup process checks the resource dictionaries of each successive parent control until it reaches the MainWindow.xaml file. If it still does not find a match, then it will look in the application Resources section in the App.xaml file.
StaticResource lookups occur once upon initialization and will suit our requirements for most of the time. When using a StaticResource to reference one resource that is to be used within another resource, the resource being used must be declared beforehand. That is to say that a StaticResource lookup from one resource cannot reference another resource that is declared after it in the resource dictionary:
<Style TargetType="{x:Type Button}"> <Setter Property="Foreground" Value="{StaticResource RedBrush}" /> </Style> <SolidColorBrush x:Key="RedBrush" Color="Red" />
The preceding XAML would result in the following error:
The resource "RedBrush" could not be resolved.
Simply moving the declaration of the brush before the style would clear this error and get the application running again. However, there are certain situations when using a StaticResource to reference a resource isn't suitable. For example, we might need our styles to update during runtime in response to some programmatic or user interaction, such as a changing of the computer theme.
In these cases, we can use a DynamicResource to reference our resources and can rest assured that our styles will update when the relevant resources are changed. Note that the resource value is not looked up until it is actually requested, so this is perfect for resources that will not be ready until after the application starts. Note the following altered example:
<Style TargetType="{x:Type Button}"> <Setter Property="Foreground" Value="{DynamicResource RedBrush}" /> </Style> <SolidColorBrush x:Key="RedBrush" Color="Red" />
In this case, there will be no compilation error, as the DynamicResource will retrieve the value whenever it is set. While it's great to have this ability, it's important not to abuse it, as using the DynamicResource will negatively affect performance. This is because they repeatedly lookup the value each time it is requested, whether the values have changed or not. For this reason, we should only ever use a DynamicResource if we really need to.
One final point about resource styles to mention here relates to scope. While this topic has been mentioned elsewhere in this book, it is outlined again here as it is essential to understand the resource lookup procedure. Application resources that are declared in the App.xaml file are available application-wide, so this is a great place to declare our common styles.
However, this is one of the furthest removed places that we can declare our styles, ignoring external resource dictionaries and theme styles. In general, the rule is that given a resource identifier conflict, the most local resources override those that are declared further away. Therefore, we can define our default styles in the application resources but retain the ability to override them locally.
Conversely, locally declared styles without an x:Key directive will be implicitly applied locally, but will not be applied to elements of the relevant type that are declared externally. We can, therefore, declare implicit styles in the Resources section of a panel for example and they will only be applied to elements of the relative type within the panel.