Resource Scope

As well as providing a ResourceDictionary for every element, FrameworkElement also provides a FindResource method to retrieve resources. Example 12-4 shows the use of this to retrieve the same resources as Example 12-3.

Example 12-4. Using FindResource

Brush b = (Brush) this.FindResource("myBrush"); String s = (String) this.FindResource("HW");

This may seem pointless—why does this FindResource method exist when we could just use the dictionary's indexer as we did in Example 12-3? The reason is that FindResource doesn't give up if the resource is not in the specified element's resource dictionary. It will search elsewhere. Example 12-5 illustrates the difference between these two approaches.

Example 12-5. FrameworkElement.Resources versus FindResource // Returns null

Brush b1 = (Brush) myGrid.Resources["myBrush"];

// Returns SolidColorBrush from Window.Resources Brush b2 = (Brush) myGrid.FindResource("myBrush");

This code uses the myGrid element from Example 12-2 instead of this. The Grid doesn't have any resources, so the b1 variable will be set to null. However, because b2 is set using FindResources instead of the resource dictionary indexer, WPF considers all of the resources in scope, not just those directly set on the Grid. It starts at the Grid element, but then examines the parent, the parent's parent, and so on, all the way to the root element. (In this case, the parent happens to be the root element, so this is a short search. But in general, it searches as many elements as it needs to.) The result is that the b2 variable is set to the same Brush object as was retrieved in Examples 12-3 and 12-4.

It doesn't stop here. If FindResource gets all the way to the root of the UI without finding the specified resource, it will then look in the application. Not only do all framework elements have a Resources property, so does the Application object. Example 12-6 shows how to define application-scope resources in markup. (If you are using the normal Visual Studio WPF project template, you would put this in the App.xaml file.)

Example 12-6. Resources at application scope <!-- App.xaml -->

<Application x:Class="MyResourcesExample.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

StartupUri="Window1.xaml" >

<Application.Resources>

<LinearGradientBrush x:Key="myBrush" StartPoint="0,0" EndPoint="1,1"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0" Color="Red"/> <GradientStop Offset="1" Color="Black"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Application.Resources> </Application>

The application scope is helpful for objects that are used throughout your application. For example, if you use styles or control templates, you would typically put these in the application resources, to ensure that you get a consistent look across all the windows in your application.

Resource searching doesn't even stop at the application level. If a resource is not present in the UI tree or the application, FindResource will finally consult the system scope, which contains resources that represent system-wide settings, such as the configured color for selected items, the correct width for a scroll bar, and styles for built-in controls. The control styles WPF adds to the system scope will be based on the user's chosen "theme" (or "visual style").

Figure 12-1 shows a typical hierarchy of resource sources. Several applications are running, each application may have several windows, and each window has a tree consisting of multiple elements. If FindResource is called on the element labeled "1" in the figure, it will first look in that element's resource dictionary. If that fails, it will keep working its way up the hierarchy through the numbered items in order, until it reaches the system resources.

WPF uses the system scope to define brushes, fonts, and metrics that the user can configure at a system-wide level. The keys for these are provided as static properties of the SystemColors, SystemFonts, and SystemParameters classes, respectively. (These classes define more than 400 resources, so they are not listed here—consult the SDK documentation for each class to see the complete set.) Example 12-7 uses the system scope to retrieve a brush for the currently configured tool tip background color. (See Chapter 13 for more information on brushes.)

Figure 12-1. Resource hierarchy

Example 12-7. Retrieving a system scope resource

Brush toolTipBackground = (Brush) myGrid.FindResource(SystemColors.InfoBrushKey);

These system resource classes use objects rather than strings as resource keys. This avoids the risk of naming collisions—system resources are always identified by a specific object, so there will never be any ambiguity between them and your own named resources.

Defining custom system-scope resources

All the built-in controls rely on the system scope to provide styles and templates suitable for the current OS theme. Without these resources, the controls would have no appearance by default. If you are writing a custom control, you will usually want to do the same thing. By providing a style for your control, you ensure that it has a default appearance. By putting that style into the system scope, you give developers the opportunity to customize the control by putting an alternative style in a narrower scope such as the application scope.

To add custom resources to the system scope, you must annotate your component with the Themelnfo custom attribute. This indicates two things: whether your component has non-theme-specific system-scope resources, and whether it has theme-specific system-scope resources. As Example 12-8 shows, this is an assembly-level attribute. It would typically go in the AssemblyInfo.cs source file.

Example 12-8. Declaring custom system-scope resources

[assembly:ThemeInfo( ResourceDictionaryLocation.None, // Theme-specific resources

ResourceDictionaryLocation.SourceAssembly // Generic resources

This example declares that the component has generic resources, but no theme-specific resources. This instructs WPF to look in the component for an embedded ResourceDictionary called themes\generic.xaml, and to add any resources in that dictionary to the system scope. (We will show how to embed resource streams later in this chapter.)

You can also specify that theme-specific resources are present by setting the first parameter of the attribute to SourceAssembly. This would cause WPF to look for an embedded resource named after the currently selected theme. Table 12-1 shows the names of the embedded resources WPF will look for. If it cannot find a resource for the current theme, it will fall back to the generic resources instead.

Table 12-1. Themes and resource names

Theme Embedded resource name

Aero (Windows Vista) themes\Aero.NormalColor.xaml

Luna Blue (Windows XP) themes\Luna.NormalColor.xaml

Luna Silver (Windows XP) themes\Luna.Metallic.xaml

Luna Olive Green (Windows XP) themes\Luna.Homestead.xaml

Royale (Windows Media Center) themes\Royale.NormalColor.xaml

Classic (Any Windows version) themes\Classic.xaml

The ResourceDictionaryLocation enumeration has one more value besides the two shown in Example 12-8: ExternalAssembly. This will cause WPF to look in a separate assembly for the resources. It will look for an assembly with a name formed by adding a period and then the current theme name to your assembly's name (which means you can specify this only for the theme-specific resources). It uses only the base theme name, without the color scheme appended. For example, if your component is called MyLibrary, and the user is running with the Windows Vista Aero theme, WPF will look for a MyLibrary.Aero component containing the themes\Aero.NormalColor.xaml resources.

For WPF to be able to find a custom system-scope resource, you must use a suitable key type: the key must incorporate information about which assembly contains the resource. For a custom control's style, you will normally use the control's Type object as a key. You typically do this by specifying a TargetType, as in Example 12-9. This automatically uses that type as the key, as well as the style's target type.

Example 12-9. Style using a Type object as key <Style TargetType="{x:Type local:MyCustomControl}">

When looking up a resource by type, WPF will locate the assembly referred to by the Type object's Assembly property. If the assembly has a ThemeInfo attribute indicating that system-scope resources are present, WPF will look for an embedded resource dictionary. In short, you simply add the ThemeInfo attribute, and put the resource streams in the same component as the custom control, and it all just works.

Sometimes it's useful to put resources other than styles into the system scope. You can do this, but you can't use a string to name the resource—a simple string won't tell WPF in which assembly it should be looking. WPF therefore provides the ComponentResourceKey type. This is a class designed to be used as a resource name. It incorporates both an identifier (which may be a string) and a Type object to indicate which assembly defines the type. WPF also defines a corresponding markup extension, offering a syntax for using these keys from XAML, which is shown in Example 12-10.

Example 12-10. Naming custom system-scope resources

<SolidColorBrush x:Key="{ComponentResourceKey {x:Type local:MyCustomType}, myBrush}" />

With a resource defined this way in your themes\generic.xaml, or in one of the theme-specific dictionaries, you can refer to the resource using the same syntax that Example 12-10 uses to name it. Because the ComponentResourceKey incorporates a type object, WPF will know which assembly defines the resource, and will be able to find it.

Using system-scope resources

The system resource classes also define static properties that let you retrieve the relevant object directly rather than having to go via the resource system. For example, SystemColors defines an InfoBrush property that returns the same value that FindResource returns when passed SystemColors.InfoBrushKey. So rather than writing the code in Example 12-7, we could have written the code in Example 12-11.

Example 12-11. Retrieving a system resource through its corresponding property Brush toolTipBackground = SystemColors.InfoBrush;

When writing code, these properties are likely to be simpler to use than the resource system. However, using the resource key properties offers three advantages. First, if you want to let the user change your application's color scheme away from the system-wide default, you can override these system settings by putting resources into the application scope. Example 12-12 shows an application resource section that defines a new application-wide value for the InfoBrushKey resource.

Example 12-12. Application overriding system colors

// (Hypothetical function for retrieving settings) Color col = GetColorFromUserSettings();

Application.Current.Resources[SystemColors.InfoBrushKey] = new SolidColorBrush(Colors.Red);

This replacement value would be returned in Example 12-7, but not in Example 12-11. This is because in Example 12-11, SystemColors has no way of knowing what scope you would like to use, so it always goes straight to the system scope.

The second advantage offered by resource keys is that they provide a straightforward way of using system-defined resources from markup. Third, you can make your application respond automatically to changes in system resources. Both of these last two benefits come from using resource references.

Was this article helpful?

0 0
Project Management Made Easy

Project Management Made Easy

What you need to know about… Project Management Made Easy! Project management consists of more than just a large building project and can encompass small projects as well. No matter what the size of your project, you need to have some sort of project management. How you manage your project has everything to do with its outcome.

Get My Free Ebook


Post a comment