(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
Someone recently asked me how WPF controls get their default templates. If you create a built-in control such as Button
, its Template
property will automatically be set to a ControlTemplate
that defines the appropriate appearance for that control. Most controls look different in the various Windows themes, so the exact template you get depends on which theme is running.
The question is: where does WPF get the default template from?
The first obvious place to look is the property metadata. Template
, like most properties of WPF controls, is a dependency property. This means it has metadata associated with it which, amongst other things, can define a default value. We can retrieve this like so:
Control.TemplateProperty.GetMetadata(typeof(Button)).DefaultValue
This retrieves the default value that the Template
property will have on an instance of Button
. The reason we pass a Type
into GetMetadata
is that WPF lets a property have different metadata for different types. This allows a property to have different default values in different contexts. For example, the default value for the Focusable
property is False
for UIElement
, but Control
overrides this to True
. (And UserControl
overrides it back to False
again.)
But this doesn't help us. It turns out that the default value for the Template
property on a Button
is null, as it is for other controls. So how does the property come to have a value by default?
The Template
property gets its value from WPF's style system. The Template
property is not special: lots of properties pick up their values this way. For example, if you are running the Windows Vista Aero theme, the Button
has its Background
, Foreground
, BorderBrush
, BorderThickness
, FocusVisualStyle
, HorizontalContentAlignment
, VerticalContentAlignment
, Padding
, and Template
properties all set by the style system. Although the Template
may seem special because it gets to define the visuals for a control, it's still just another property.
There's one problem with this explanation. If you look at an ordinary WPF Button
at runtime, its Style
property will usually be null. How can the Template
(and all these other properties) come from a style if Style
is null?
WPF elements can have two styles associated with them. The Style
property can hold an 'explicit' style. (This is null by default, but can be set in a variety of ways.) But a control will also have a so-called theme style. And only the explicit style is directly visible, there is evidence of the presence of the theme style. For example, look at the BaseValueSource
enumeration, which enumerates all the possible places that a dependency property's value may have come from. It includes both Style
and DefaultStyle
values, the latter indicating that the value came from the default style for the current theme. WPF returns a member of this enumeration when we ask it where it got the value for a particular property:
DependencyPropertyHelper.GetValueSource(myButton, Button.TemplateProperty).BaseValueSource
If myButton
refers to a normal Button
here, this will evaluate to DefaultStyle
, indicating that the Template
property came from the theme style. So we now know exactly how the Template
property acquired its value...up to a point. Of course the next question is: where does this DefaultStyle
come from? But first a quick digression.
You might be wondering why elements need two styles. Well imagine if they only had one. Think what would happen with the following:
<Grid> <Grid.Resources> <Style TargetType="{x:Type Button}"> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" /> </Style> </Grid.Resources> <Button Content="Click me" /> </Grid>
The Button
in this example has an explicit style. It doesn't look all that explicit, since the Style
property has not been set directly. However, we've put a Style
with a TargetType
of Button
's type object, and this has the effect of applying the Style
to any Button
inside that Grid
.
Note: what I've written here contradicts what the SDK currently says. It says that an explicit style is one that is "referenced by non-type Key" which suggests that this doesn't count as an explicit style. However, if we use the DependencyPropertyHelper
to ask where the Button
in this example got its HorizontalAlignment
and VerticalAlignment
properties from, it says: Style
, which the documentation says means the value "is from a style setter of an explicit style."
So experimentation indicates that an 'explicit style' is one that is not the default style from the theme. I think the documentation for "Dependency Property Value Precedence" is simply misleading right now. In fact a simpler way of looking at it is this: an explicit style is the one in the Style
property. Given the XAML above, if you read the Button
's Style
property, you'll see that it actually contains a reference to the Style
defined in the Grid
's resources.
Anyway, having cleared up that this is indeed an explicit style, why does this distinction matter? Suppose elements had just one style: what would happen to all the other properties normally set by the style in this case? Remember that Background
, Foreground
, BorderBrush
, BorderThickness
, FocusVisualStyle
, HorizontalContentAlignment
, VerticalContentAlignment
, Padding
, and Template
all get their value from the theme style by default. If the theme style were simply replaced with the custom style shown above, all of these properties would revert to their unstyled defaults. And since the default value for Template
is null, this means the control would lose its appearance!
You could solve this by setting a custom style's BasedOn
property to refer to the original style. But that would be tedious, seeing as how every single style would need to do that. So instead, WPF just remembers the original theme style even when an explicit style is supplied. Setters in the explicit style take precedence over the theme style. But if the explicit style does not supply a setter for a property that the theme style does, the theme style gets to supply that property's value. This is why a control doesn't vanish the moment you supply a custom style: the default theme style is still in place, and will still supply the Template
and various other properties if your custom style doesn't supply different values.
We still don't really have a satisfactory answer to the original question. We now know that by default, a control's Template
comes from the theme style. But where does the theme style come from? WPF defines a property to hold it. However, it's not public. This doesn't stop us from taking a look at it using a technique that YOU SHOULD NOT USE IN PRODUCTION CODE:
Style ts = typeof(Button).GetProperty("ThemeStyle", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(myButton, null) as Style;
By all means do that to satisfy you're curiosity, but please don't do that in live code, because there's no guarantee that property will be there in future versions of WPF. In any case, we don't need to do that. As long as you've not defined a custom default style at the application scope, the following code will find the system style:
Style ts = Application.Current.FindResource(typeof(Button));
You can verify for yourself that this returns the very same object instance. But why would it do that? Well, when you call FindResource
on Application
(or a FrameworkElement
for that matter) it will walk its way up the resource tree, and eventually hit the system scope. The system scope is where the default theme styles are defined. (And in fact, WPF goes straight to the system scope when locating the default theme style. However, there's no public API for doing that, which is why the code shown here starts at the application scope instead; it still ends up at the system scope.)
The answer is still not looking especially concrete. We know that the Template
property typically gets its default value from the theme style, and that the theme style comes from the system scope resources. But where do those come from? If you've ever written a custom control, then you'll know, because the way custom controls add theme-specific resources to the system scope turns out to be the same as the way WPF adds its own theme-specific resource to the same scope.
It works like this. Style resources are identified by a Type
object. And when WPF looks for a style in the system scope, it looks at the identifying Type
object's Assembly
property. It examines that assembly for a ThemeInfo
custom attribute. If that attribute is present, WPF examines its ThemeInfoDictionaryLocation
property. If this is set to None
, then it knows no theme-specific resources are present for this type. Otherwise, if it is set to SourceAssembly
, it knows that the assembly that defines the type also contains theme-specific resources. If it is set to ExternalAssembly
, it knows that theme-specific resources are available in a separate assembly whose name is formed by adding the theme name to the base assembly name.
Let's follow that through with Button
. The Button
type is defined in the PresentationFramework
assembly. This assembly has the ThemeInfo
attribute applied, and the ThemeInfoDictionaryLocation
is set to ExternalAssembly
. This means that if we're running the Aero theme, WPF should look for an assembly called PresentationFramework.Aero
for Aero-specific resources. (You can see this very assembly in your GAC.)
Having worked out which assembly it's going to look in, it will then look for a specific named resource. The resource name always starts "/themes/" and then is based on the theme name, including the colour scheme. For example, Aero resources would be in "/themes/Aero.NormalColor.xaml". Windows 2000-style resources would be in "/themes/Classic.xaml" because there will only ever be one colour scheme for that theme. Luna is available in various colour, so "/themes/Luna.Metallic.xaml" would contain the silver resources, while "/themes/Luna.Homestead.xaml" contains olive, while "/themes/Luna.NormalColor.xaml" contains the normal tellytubby blue resources.
You can load up these resource dictionaries for yourself if you want. You need to build a relative pack URI that incorporates the component name and resource name. Here's one for WPF's Aero resources:
"/
PresentationFramework.Aero
, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35;component/themes/Aero.NormalColor.xaml"
You then wrap this in a relative Uri
object and pass it to Application.LoadComponent
. It will return you a ResourceDictionary
containing all the Aero-specific system-scope resources. Here's a function that does most of the work:
public static ResourceDictionary LoadThemeDictionary(Type t, string themeName, string colorScheme) { Assembly controlAssembly = t.Assembly; AssemblyName themeAssemblyName = controlAssembly.GetName(); object[] attrs = controlAssembly.GetCustomAttributes( typeof(ThemeInfoAttribute), false); if (attrs.Length > 0) { ThemeInfoAttribute ti = (ThemeInfoAttribute) attrs[0]; if (ti.ThemeDictionaryLocation == ResourceDictionaryLocation.None) { // There are no theme-specific resources. return null; } if (ti.ThemeDictionaryLocation == ResourceDictionaryLocation.ExternalAssembly) { themeAssemblyName.Name += "." + themeName; } } string relativePackUriForResources = "/" + themeAssemblyName.FullName + ";component/themes/" + themeName + "." + colorScheme + ".xaml"; Uri resourceLocater = new System.Uri( relativePackUriForResources, System.UriKind.Relative); return Application.LoadComponent(resourceLocater) as ResourceDictionary; }
This code isn't quite the whole story, because it doesn't do fallback to generic resources when theme-specific ones are not present. A "/themes/generic.xaml" can be present to use when theme-specific resources cannot be found. However, it does finally get us to the answer to: where does the template come from?
We can call this method passing in typeof
(
Button)
, "Aero", and "NormalColor", and we will be returned a ResourceDictionary
. This dictionary will contain a Style
for the key typeof
(
Button)
. This style actually contains very little, but its BasedOn
property refers to a Style
for ButtonBase
, and that is what supplies a setter that determines the button's default value for the Template
property.