Chapter 21. Resources


Suppose you're coding some XAML for a window or a dialog box, and you decide you'd like to use two different font sizes for the controls. Some controls in the window will get the larger font size and some will get the smaller. You probably know which controls will get which font size, but you're not quite sure yet what the actual font sizes will be. Perhaps you'd like to experiment first before settling on the final values.

The naive approach is to insert hard-coded FontSize values in the XAML, like so:

FontSize="14pt" 


If you later decide that you actually want something a little larger or smaller, you could just perform a search-and-replace. Although search-and-replace may work on a small scale, as a programmer you know that it's not a general solution to problems of this sort. Suppose you were dealing with a complex gradient brush rather than a simple font size. You might begin by copying and pasting a gradient brush throughout the program, but if you ever need to tweak that brush, you'll need to do it in a bunch of places.

If you faced this problem in C#, you wouldn't duplicate the gradient brush code or hard-code the font size values. You'd define variables for these objects, orto clarify your intentions and improve efficiencyyou could define a couple of constant fields in the window class:

const double fontsizeLarge = 14 / 0.75; const double fontsizeSmall = 11 / 0.75; 


You could alternatively define them as static read-only values:

static readonly double fontsizeLarge = 14 / 0.75; static readonly double fontsizeSmall = 11 / 0.75; 


The difference is that constants are evaluated at compile time and the values substituted wherever they're used, while statics are evaluated at run time.

This technique is so common and so useful in procedural programming that an equivalent facility in XAML would be quite valuable. Fortunately, it exists. You can reuse objects in XAML by first defining them as resources.

The resources I'll be discussing in this chapter are quite different from resources discussed earlier in this book. I've previously shown you how to use Microsoft Visual Studio to indicate that certain files included in a project are to be compiled with a Build Action of Resource. These resources are perhaps more accurately termed assembly resources. Most often, assembly resources are binary files such as icons and bitmaps, but in Chapter 19 I also showed you how to use this technique with XML files. These assembly resources are stored in the assembly (the executable file or a dynamic-link library) and are accessible by defining a Uri object referencing the resource's original file name.

The resources in this chapter are sometimes referred to as locally defined resources because they are defined in XAML (or sometimes in C# code) and they are usually associated with an element, control, page, or window in the application. A particular resource is available only within the element in which the resource is defined and within the children of that element. You can think of these resources as the compensation XAML offers in place of C# static read-only fields. Like static read-only fields, resource objects are created once at run time and shared by elements that reference them.

Resources are stored in an object of type ResourceDictionary, and three very fundamental classesFrameworkElement, FrameworkContentElement, and Applicationall define a property named Resources of type ResourceDictionary. Each item in the ResourceDictionary is stored along with a key to identify the object. Generally these keys are just text strings. XAML defines an attribute of x:Key specifically for the purposes of defining resource keys.

Any element that derives from FrameworkElement can have a Resources collection. Almost always, the Resources section is defined with property element syntax at the very top of the element:

<StackPanel>     <StackPanel.Resources>         ...     </StackPanel.Resources>     ... </StackPanel> 


The resources defined within that Resources section can be used throughout the StackPanel and by any children of the StackPanel. Each resource in the Resources section has the following form:

<SomeType x:Key="mykey" ...>     ... </SomeType> 


You can set properties of that object with either attribute syntax or property element syntax. XAML elements can then reference the resource with the key using a markup extension. As the term implies, a markup extension is a special keyword that has been defined for use in XAML. The particular markup extension you use with resources is named StaticResource.

I began this chapter describing a problem involving the use of two different font sizes. The following stand-alone XAML file shows how to define two font-size resources in the Resources collection of a StackPanel and then how to access those resources by child elements of the StackPanel.

FontSizeResources.xaml

[View full width]

<!-- === ================================================= FontSizeResources.xaml (c) 2006 by Charles Petzold ============================================= ======= --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" xmlns:s="clr-namespace :System;assembly=mscorlib"> <StackPanel.Resources> <s:Double x:Key="fontsizeLarge"> 18.7 </s:Double> <s:Double x:Key="fontsizeSmall"> 14.7 </s:Double> </StackPanel.Resources> <Button HorizontalAlignment="Center" VerticalAlignment="Center" Margin="24"> <Button.FontSize> <StaticResource ResourceKey="fontsizeLarge" /> </Button.FontSize> Button with large FontSize </Button> <Button HorizontalAlignment="Center" VerticalAlignment="Center" Margin="24" FontSize="{StaticResource fontsizeSmall}" > Button with small FontSize </Button> </StackPanel>



Notice that the StackPanel element tag defines an XML namespace prefix of s for the System namespace (clr-namespace:System;assembly=mscorlib), which allows referencing the Double structure in the Resources collection.

The Resources section of the StackPanel contains definitions of two Double objects with keys of "fontsizeLarge" and "fontsizeSmall." Within any particular Resources dictionary, the keys must be unique. The values of 18.7 and 14.7 are equivalent to 14 points and 11 points.

The StackPanel or child elements of the StackPanel can make use of these resources in one of two ways, both involving the StaticResource markup extension. The first Button accesses the resource using property element syntax for FontSize with an element of StaticResource and an attribute of ResourceKey to indicate the key of the item:

<Button.FontSize>     <StaticResource ResourceKey="fontsizeLarge" /> </Button.FontSize> 


The more common syntax is shown for the second Button. The FontSize attribute is set to a string with the words StaticResource and the key name all enclosed in curly brackets:

FontSize="{StaticResource fontsizeSmall}" 


Take a close look at that syntaxyou'll be seeing it a lot in this chapter, and again in Chapter 23 when I take on the subject of data binding. The curly brackets indicate that the expression inside is a markup extension. There is no StaticResource class. There is, however, a class named StaticResourceExtension that inherits from MarkupExtension and includes a property named ResourceKey. StaticResource is classified as a markup extension because it provides a way to do something in XAML that normally would only be possible in procedural code. The StaticResourceExtension class is essentially providing the value from the dictionary based on the specified key.

You'll be seeing two other markup extensions in this chapter: x:Static and DynamicResource, and they also will be enclosed in curly brackets. The curly brackets indicate to the XAML parser that a markup extension is present. No additional quotation marks are allowed within the curly brackets.

You might, on a rare occasion, need to begin a text string with some curly brackets that do not involve a markup extension:

<!-- Won't work right! --> <TextBlock Text="{just a little text in here}" /> 


To prevent the XAML parser from searching for (and failing to find) a markup extension named just, insert an escape sequence in front consisting of a pair of empty curly brackets:

<!-- Works just fine! --> <TextBlock Text="{}{just a little text in here}" /> 


The Resources section is almost always defined at the very top of an element because any resource must be defined earlier in the file than when it is referenced. Forward references of resources are not allowed.

Although all the keys in a particular Resources collection must be unique, the same keys can be used in two different Resources collections. When a resource must be located, the search begins with the Resources collection of the element referencing the resource and continues up through the tree until the key is found. The following stand-alone XAML file illustrates this process.

ResourceLookupDemo.xaml

[View full width]

<!-- === ================================================== ResourceLookupDemo.xaml (c) 2006 by Charles Petzold ============================================= ======== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft .com/winfx/2006/xaml" Orientation="Horizontal"> <StackPanel.Resources> <SolidColorBrush x:Key="brushText" Color="Blue" /> </StackPanel.Resources> <StackPanel> <StackPanel.Resources> <SolidColorBrush x:Key="brushText" Color="Red" /> </StackPanel.Resources> <Button HorizontalAlignment="Center" VerticalAlignment="Center" Margin="24" Foreground="{StaticResource brushText}"> Button with Red text </Button> </StackPanel> <StackPanel> <Button HorizontalAlignment="Center" VerticalAlignment="Center" Margin="24" Foreground="{StaticResource brushText}"> Button with Blue text </Button> </StackPanel> </StackPanel>



Three StackPanel elements are defined here. The first has a horizontal orientation; the other two StackPanel elements are children. For simplicity, each of these two StackPanel children contains just one button.

The parent StackPanel has a Resources collection defining a SolidColorBrush with a key of "brushText" and a color of blue. The first child StackPanel also has a Resources collection with another SolidColorBrush that also has a key of "brushText" but a color of red. Both buttons set their Foreground properties with a StaticResource extension referencing the "brushText" key. The first button (in the StackPanel with the red brush) has red text. The second button is in a StackPanel that has no resource named "brushText," so it uses the resource from the parent StackPanel. Its text is colored blue.

Defining resources with the same name is a powerful technique, particularly with styles, which I'll introduce in Chapter 24. Styles give you the ability to define properties used for multiple elements, and even to define how these elements react to particular events and property changes. In real-life WPF programs, most Resources collections are devoted to defining and redefining styles. Because styles are so important, I thought it best to introduce the topic of resources first to give you a good foundation for understanding these underlying concepts. Just keep in mind that if something appears to be missing from this chaptermainly, the use of resources to define a bunch of properties for particular elements and controlsit's coming in Chapter 24.

Resources are shared. Only one object is created for each resource, and even that object won't be created if the resource isn't referenced.

You may wonder, "Can I define an element or control as a resource?" Yes, you can. For example, you can include the following in one of the Resources sections in ResourceLookupDemo.xaml:

<Button x:Key="btn"         FontSize="24">     Resource Button </Button> 


You can then use that Button as a child in the StackPanel (or a child StackPanel, depending where you've defined the resource) using the following syntax:

<StaticResource ResourceKey="btn" /> 


It works, but you can't do it twice. The Button object created as the resource is just one object, and if that Button is a child of a panel, it can't be another child of that same panel or a child of another panel. And notice you can't change anything about the Button when you reference it in the StaticResource element. You're not really gaining anything by making the Button a resource. So what's the point?

If you think you really have a need to define controls and other elements as resources, you're probably thinking about using the resource to define some properties of the element but not all the properties. It's very likely that what you really want is a style, and that you'll get in Chapter 24.

Although resources are almost always defined in XAML rather than procedural code, you can add an object to an element's Resources collection with C# code such as this:

stack.Resources.Add("brushText", new SolidColorBrush(Colors.Blue)); 


Here it's quite obvious that the resource is only a single object that's possibly shared among multiple elements or controls. The Add method is defined by ResourceDictionary. The first argument is the key and is defined as type object, but it's common to use strings. Each of the three main classes that define a Resources collection (FrameworkElement, FrameworkContentElement, and Application) also defines a method named FindResource that locates a resource with a particular key. This is the method that the StaticResourceExtension undoubtedly uses to locate a resource.

What's interesting is that calling the FindResource method for a particular element might find something from the element's Resources collection, but it won't stop there. It can also find a resource with the specified key from one of the element's ancestors in the element tree. I would guess that FindResource is implemented something like this:

public object FindResource(object key) {     object obj = Resources[key];     if (obj != null)         return obj;     if (Parent != null)         return Parent.FindResource(key);     return Application.Current.FindResource(key); } 


The recursive search up the element tree is what makes FindResource much more valuable than simply indexing the Resources property with the key. Notice also that when the element tree is exhausted, FindResource checks the Resources dictionary in the Application. You canand shoulduse the Resources collection of the Application object for application-wide settings, styles, and themes.

Up until now I've been recommending that you create empty projects in Visual Studio so that you have a better understanding of WPF itself without getting distracted by what Visual Studio provides for you. Now that you've been introduced to resources, it's probably safe to let Visual Studio impose its obstinate will on your programming style, at least experimentally.

Let's use Visual Studio to create a project of type Windows Presentation Foundation Application and give it a name of GradientBrushResourceDemo. Visual Studio creates a file named MyApp.xaml with a Resources section already defined and ready for your input:

MyApp.xaml

[View full width]

<Application x: xmlns="http://schemas.microsoft.com/winfx /2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx /2006/xaml" StartupUri="Window1.xaml" > <Application.Resources> </Application.Resources> </Application>



That's how important this application-wide Resources section is considered to be in the context of WPF programming! Let's use that Resources section to define an application-wide gradient brush, so that MyApp.xaml now looks like this:

MyApp.xaml

[View full width]

<Application x: 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="brushGradient" StartPoint="0, 0" EndPoint="1, 1"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0" Color="Black" /> <GradientStop Offset="0.5" Color="Green" /> <GradientStop Offset="1" Color="Gold" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Application.Resources> </Application>



Visual Studio also creates a MyApp.xaml.cs code-behind file for MyApp.xaml, but this file doesn't do much. The Window1.xaml file that Visual Studio creates defines a Window element and a Grid by default.

Window1.xaml

[View full width]

<Window x: xmlns="http://schemas.microsoft.com/winfx/2006 /xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx /2006/xaml" Title="GradientBrushResourceDemo" Height="300" Width="300" > <Grid> </Grid> </Window>



The initial Window1.xaml.cs code-behind file simply contains a call to InitializeComponent.

Window1.xaml.cs

using System; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace GradientBrushResourceDemo {     /// <summary>     /// Interaction logic for Window1.xaml     /// </summary>     public partial class Window1 : Window     {         public Window1()         {             InitializeComponent();         }     } } 



To that code I added a resource to the window with one statement of C#.

Window1.xaml.cs

[View full width]

using System; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace GradientBrushResourceDemo { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { Resources.Add("thicknessMargin", new Thickness(24, 12, 24, 23)); InitializeComponent(); } } }



In the Window1.xaml file, a StackPanel can replace the Grid, and then four TextBlock elements can use the LinearGradientBrush resource and the Thickness resource.

Window1.xaml

[View full width]

<Window x: xmlns="http://schemas.microsoft.com/winfx/2006 /xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx /2006/xaml" Title="GradientBrushResourceDemo" Height="300" Width="300" > <StackPanel> <TextBlock Margin="{StaticResource thicknessMargin}" Foreground="{StaticResource brushGradient}"> Gradient text </TextBlock> <TextBlock Margin="{StaticResource thicknessMargin}" Foreground="{StaticResource brushGradient}"> Of black, green, and gold </TextBlock> <TextBlock Margin="{StaticResource thicknessMargin}" Foreground="{StaticResource brushGradient}"> Makes an app pretty, </TextBlock> <TextBlock Margin="{StaticResource thicknessMargin}" Foreground="{StaticResource brushGradient}"> Makes an app bold. </TextBlock> </StackPanel> </Window>



It's no big secret that I don't like using the prefabricated Visual Studio projects in creating sample applications for my books. One problem is that I feel a need to rename everything so that my file names are more varied than MyApp and Window1. But now that you've seen what the Application.Resource tag means and how to use it, there's no problem if you want to use the Visual Studio projects and the XAML designer. (Just don't tell me about it.)

At the beginning of this chapter I described how you might define two different font sizes in a program as static read-only fields. Interestingly enough, XAML defines a markup extension named x:Static specifically to reference static properties or fields. It also works with enumeration members.

For example, suppose you want to set the Content property of a Button to a static property named SomeStaticProp from a class named SomeClass. The markup extension syntax is:

Content="{x:Static SomeClass:SomeStaticProp}" 


Or, you can use an x:Static element within a property element syntax:

<Button.Content>     <x:Static Member="SomeClass:SomeStaticProp" /> </Button.Content> 


The type of the static field or property should match the type of the property you're setting, or there should exist a type converter to help the process. (Of course, the Content property is of type object, so anything goes.) For example, if you want to make a particular element the same height as the caption bar, you use:

Height="{x:Static SystemParameters.CaptionHeight}" 


You aren't restricted to static properties or fields defined within the Windows Presentation Foundation, but if you access non-WPF classes, you'll need an XML namespace declaration for the CLR namespace in which those classes are found.

Here's a stand-alone XAML program that associates an XML prefix of s with the System namespace to display information from static properties in the Environment class.

EnvironmentInfo.xaml

[View full width]

<!-- === =============================================== EnvironmentInfo.xaml (c) 2006 by Charles Petzold ============================================= ===== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" xmlns:s="clr-namespace :System;assembly=mscorlib"> <TextBlock> <Label Content="Operating System Version: " /> <Label Content="{x:Static s:Environment .OSVersion}" /> <LineBreak /> <Label Content=".NET Version: " /> <Label Content="{x:Static s:Environment .Version}" /> <LineBreak /> <Label Content="Machine Name: " /> <Label Content="{x:Static s:Environment .MachineName}" /> <LineBreak /> <Label Content="User Name: " /> <Label Content="{x:Static s:Environment .UserName}" /> <LineBreak /> <Label Content="User Domain Name: " /> <Label Content="{x:Static s:Environment .UserDomainName}" /> <LineBreak /> <Label Content="System Directory: " /> <Label Content="{x:Static s:Environment .SystemDirectory}" /> <LineBreak /> <Label Content="Current Directory: " /> <Label Content="{x:Static s:Environment .CurrentDirectory}" /> <LineBreak /> <Label Content="Command Line: " /> <Label Content="{x:Static s:Environment .CommandLine}" /> </TextBlock> </StackPanel>



This program is only interested in displaying the text rendition of these properties. Most of these properties return strings, but two do not. OSVersion, which reveals the version of Microsoft Windows currently running, is of type OperatingSystem, and Version (which indicates the .NET version) is of type Version. The ToString methods of these classes fortunately format the information so that it's readable.

These two x:Static markup expressions couldn't simply be set to the Text properties of a TextBlock (for example). There is no automatic conversion of these non-string objects to string. Instead, I set the x:Static expressions to the Content properties of Label controls. The Content property, you'll recall, can be set to any object, and the object will be rendered with its ToString method. Making these Label elements the children of a TextBlock (in which case they actually become part of InlineUIContainer elements) allows interspersing LineBreak elements to separate the output into multiple lines.

Although you can run this file in XAML Cruncher or an equivalent program, it will not run in Internet Explorer. For all items except OSVersion and Version, the program requires security permissions not allowed when running XAML in Internet Explorer.

Another approach to using x:Static involves defining static fields or properties in your own C# source code and then accessing them from the project's XAML files. The next project I'll be showing you is named AccessStaticFields. The project contains an XAML file and a C# file that contribute to the same Window class as usual, but the project also contains a C# file named Constants.cs with a class named Constants that contains three static read-only fields and properties. Here's that all-static class.

Constants.cs

[View full width]

//------------------------------------------ // Constants.cs (c) 2006 by Charles Petzold //------------------------------------------ using System; using System.Windows; using System.Windows.Media; namespace Petzold.AccessStaticFields { public static class Constants { // Public static members. public static readonly FontFamily fntfam = new FontFamily("Times New Roman Italic"); public static double FontSize { get { return 72 / 0.75; } } public static readonly LinearGradientBrush brush = new LinearGradientBrush(Colors .LightGray, Colors.DarkGray, new Point(0, 0), new Point(1, 1)); } }



I've defined two of these items as static read-only fields and one as a static read-only property. That's just for variety; it doesn't really matter in this example. (Nor do they need to be read-only, but these fields can't be set from within an XAML file, so making them something other than read-only doesn't add any functionality.) Because these static fields and properties are defined as part of your own source code rather than the standard WPF assemblies, the XAML file needs an XML namespace declaration to define a prefix that is associated with the namespace of the class in which the fields or properties are located.

Here's the XAML file that associates the XML prefix src with the CLR namespace Petzold.AccessStaticFields. The file then uses the x:Static markup extension to access the static fields and properties. Two of these markup extensions use attribute syntax while the third uses property element syntax (again, just for variety).

AccessStaticFields.xaml

[View full width]

<!-- === ================================================== AccessStaticFields.xaml (c) 2006 by Charles Petzold ============================================= ======== --> <Window xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" xmlns:src="/books/4/266/1/html/2/clr-namespace:Petzold .AccessStaticFields" x:0" width="14" height="9" align="left" src="/books/4/266/1/html/2/images/ccc.gif" />.AccessStaticFields" Title="Access Static Fields" SizeToContent="WidthAndHeight"> <TextBlock Background="{x:Static src :Constants.brush}" FontSize="{x:Static src:Constants .FontSize}" TextAlignment="Center"> <TextBlock.FontFamily> <x:Static Member="src:Constants.fntfam" /> </TextBlock.FontFamily> Properties from<LineBreak />Static Fields </TextBlock> </Window>



The code-behind file is trivial.

AccessStaticFields.cs

//--------------------------------------------------- // AccessStaticFields.cs (c) 2006 by Charles Petzold //--------------------------------------------------- using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace Petzold.AccessStaticFields {     public partial class AccessStaticFields : Window     {         [STAThread]         public static void Main()         {             Application app = new Application();             app.Run(new AccessStaticFields());         }         public AccessStaticFields()         {             InitializeComponent();         }     } } 



The static fields and properties don't have to be in their own file, of course. In an early version of this project I put them right into the C# part of the AccessStaticFields class, and the x:Static markup extensions referenced the src:AccessStaticFields class rather than the src:Constants class. But I rather liked the idea of devoting an entire static-only class to a bunch of constants used by the application.

You now know how to define objects as resources and reference those objects with the StaticResource markup extension. You also know how to reference static properties and fields of classes using x:Static. What's missing here is the ability to reference instance properties and fields of a particular object. That job requires specifying both an object and a property of that object, and the syntax goes beyond what StaticResource and x:Static can handle. It's a job for data binding, and you'll see how to do it in Chapter 23.

Here's another example of x:Static. This stand-alone XAML file sets both the Content and Foreground attributes of a Label control using x:Static expressions.

DisplayCurrentDateTime.xaml

[View full width]

<!-- === ====================================================== DisplayCurrentDateTime.xaml (c) 2006 by Charles Petzold ============================================= ============ --> <Label xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx /2006/xaml" xmlns:s="clr-namespace :System;assembly=mscorlib" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48" Content="{x:Static s:DateTime.Now}" Foreground="{x:Static SystemColors .ActiveCaptionBrush}" />



The DateTime.Now object is of type DateTime and the Content property displays the string returned from the structure's ToString method, so the result is nicely formatted. The last line in the XAML file sets the Foreground property to the Brush object from the static SystemColors.ActiveCaptionBrush property, so the text will be the same color as the window's caption bar.

I hope you're not expecting the time to be automatically updated with each passing second! If you're using XAML Cruncher (or an equivalent program) to run DisplayCurrentDateTime.xaml, the DateTime.Now property is accessed just once as the Label control is created. If you just type an innocuous space into the XAML file or press F6, XAML Cruncher will pass the "new" version to XamlReader.Load again and the time will be updated.

What do you suppose happens when system colors change? Do you think the program will automatically update the Label foreground color? Try it! Invoke the Display applet of Control Panel by right-clicking on the desktop and selecting Properties from the context menu. Click the Appearance tab and change the Color Scheme to something else. The default is Blue; the alternatives are Olive Green or Silver. Click Apply or OK. (With Microsoft Windows Vista, right-click the desktop and select Personalize from the context menu and then Desktop Colors from the list.)

As Windows applies the new system colors, you'll see the caption bars of all programs currently running under Windows change colors. The caption bar of XAML Cruncher changes color as well, but the color of the text displayed by the Label remains the same.

Are you disappointed? You probably shouldn't be surprised. It's the same situation as the date and time content. The static property SystemColors.ActiveCaptionBrush is accessed just once when the Label is being created and there's no automatic mechanism to update the control when that property changes. Of course, just as with the date and time, if you type an innocuous space into the DisplayCurrentDateTime.xaml file in XAML Cruncher or press F6, the file will be reparsed and the Label recreated with the new system color.

Would you like the Label foreground color to be automatically updated when system colors change? If so, it's certainly within your grasp.

The SystemColors, SystemParameters, and SystemFonts classes all have huge collections of static properties that an XAML file can access using the x:Static markup extension. If you've looked at these three classes, you may have noticed something quite odd: All the static properties exist in pairs. For every property named Whatever, there's also a property named WhateverKey. All properties ending with the word Key return objects of type ResourceKey.

The static SystemColors.ActiveCaptionBrush property returns an object of type SolidColorBrush, so it makes perfect sense that this object can be assigned to the Foreground property of Label, which requires an object of type Brush.

The SystemColors.ActiveCaptionBrushKey property returns an object of type ResourceKey. This ResourceKey object should also provide some way to access that same brush obtained from SystemColors.ActionCaptionBrush. The ResourceKey is a key into a dictionary, just like the keys that you can define in XAML resources using the x:Key attribute. The big difference with the ResourceKey object is that it includes a property of type Assembly so that the XAML parser knows the assembly in which the dictionary is stored.

It should be possible to access the brush using the StaticResource markup extension with the key returned by SystemColors.ActiveCaptionBrushKey. Perhaps something like this will work:

Foreground="{StaticResource SystemColors.ActiveCaptionBrushKey}" 


Well, no. That doesn't work. If you try substituting this line for the Foreground attribute in DisplayCurrentDateTime.xaml, you'll get an error message that it can't find the resource. And this makes sense if you think about it. The parser is looking for a resource with a text key of "SystemColors.ActiveCaptionBrushKey." What we really want is the resource that's referenced by the key returned from the static property SystemColors.ActiveCaptionBrushKey.

It's not as elusive as it seems. Consider that this markup extension returns an object of type SolidColorBrush:

{x:Static SystemColors.ActiveCaptionBrush} 


So this markup extension must return an object of type ResourceKey:

{x:Static SystemColors.ActiveCaptionBrushKey} 


An object of type ResourceKey is exactly what StaticResource wants. So with a great leap of insight and daring, you try nesting an x:Static expression inside a StaticResource expression:

Foreground="{StaticResource {x:Static SystemColors.ActiveCaptionBrushKey}}" 


And that works! Notice that one set of curly braces is nested within the other set so that the entire expression terminates with two right curly braces. This new Foreground setting is functionally equivalent to the original setting:

Foreground="{x:Static SystemColors.ActiveCaptionBrush}" 


The resource referenced by SystemColors.ActiveCaptionBrushKey is a SolidColorBrush that is the same as the brush returned directly from SystemColors.ActiveCaptionBrush.

However, the alternative syntax seems to offer no benefits. If you run the new XAML file and change system colors, the foreground color of the Label does not automatically change.

Now let's make one little change to the alternative syntax. Let's change StaticResource to DynamicResource, which is the third and final markup extension I'll introduce in this chapter:

Foreground="{DynamicResource {x:Static SystemColors.ActiveCaptionBrushKey}}" 


And it works! Now if you change the system colors, you'll see the Label text color change along with the caption bars.

Just for the sake of completeness, you might be interested in the property element syntax of DynamicResource. In DisplayCurrentDateTime.xaml, an end tag for Label needs to be added so that the Foreground property can appear as a property element:

<Label ... >     <Label.Foreground>         <DynamicResource>             <DynamicResource.ResourceKey>                 <x:Static Member="SystemColors.ActiveCaptionBrushKey" />             </DynamicResource.ResourceKey>         </DynamicResource>     </Label.Foreground> </Label> 


StaticResource and DynamicResource represent two different approaches to accessing resources. Both require keys and use those keys to access objects. With StaticResource, the key is used to access the object once, and the object is retained. When you use DynamicResource, the key is retained and the object is accessed when it's needed.

When a user changes system colors, Windows broadcasts a system-wide message that the colors have been changed. Applications customarily respond to this message by invalidating their windows. In WPF, this invalidation translates into an InvalidateVisual call, which means that every element gets a call to OnRender. At that time, if an element's Foreground property (for example) references a dynamic resource, the retained key is used to access the brush. It's not as if the whole element is being recreated, however. It's nowhere close. When you change system colors and the Label text color changes, the Label content remains the same: The date and time that the control displays is not updated.

The primary purpose of DynamicResource is to access system resources such as system colors. Don't try to overburden DynamicResource with too many expectations. There's no concept of notification when a resource has changed. If you need your controls and elements to update themselves based on property changes of other objects (and you probably will), you want to use data binding, which I cover in Chapter 23.

Normally you cannot make a forward reference to a resource. In other words, a resource referenced by a StaticResource markup extension must already have been defined in the file or in an ancestor element. But there might come a time when it's convenient to reference the resource before it's defined. For example, a panel start tag might contain a Background attribute referencing a resource that's defined in a Resources section that follows:

<StackPanel Background="{StaticResource mybrush}">     <StackPanel.Resources>         <SolidColorBrush x:Key="mybrush" ... />     </StackPanel.Resources>     ... 


This won't work. You could remove the Background attribute from the start tag and use property element syntax instead:

<StackPanel>     <StackPanel.Resources>         <SolidColorBrush x:Key="mybrush" ... />     </StackPanel.Resources>     <StackPanel.Background>         <StaticResource ResourceKey="mybrush" />     </StackPanel.Background>     ... 


Or, you could change StaticResource to DynamicResource, in which case the access of the resource is deferred until the resource is actually needed to render the panel.

The brushes you create as resources can themselves be based on system colors. The following stand-alone XAML defines two brushes as resources. A LinearGradientBrush defines a gradient between the active caption color and the inactive caption color. The second brush is a SolidColorBrush that similarly uses DynamicResource with SystemColors.ActiveCaptionColorKey.

DynamicResourceDemo.xaml

[View full width]

<!-- === =================================================== DynamicResourceDemo.xaml (c) 2006 by Charles Petzold ============================================= ========= --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" Background="{DynamicResource {x:Static SystemColors .InactiveCaptionBrushKey}}"> <StackPanel.Resources> <LinearGradientBrush x:Key="dynabrush1" StartPoint="0 0" EndPoint="1 1"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0" Color="{DynamicResource {x:Static SystemColors .ActiveCaptionColorKey}}" /> <GradientStop Offset="1" Color="{DynamicResource {x:Static SystemColors .InactiveCaptionColorKey}}" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> <SolidColorBrush x:Key="dynabrush2" Color="{DynamicResource {x:Static SystemColors .ActiveCaptionColorKey}}" /> </StackPanel.Resources> <Label HorizontalAlignment="Center" FontSize="96" Content="Dynamic Resources" Background="{StaticResource dynabrush1}" Foreground="{StaticResource dynabrush2}" /> </StackPanel>



Notice that the two resources use DynamicResource to reference SystemColors.ActiveCaptionColorKey and SystemColors.InactiveCaptionColorKey. These are keys (because they are used with DynamicResource), but the keys reference colors rather than brushes because they are used to set the Color property of the two GradientStop objects.

The program colors itself in three ways. Toward the top, you'll see that the background of the StackPanel is a DynamicResource based on SystemColors.InactiveCaptionBrushKey. The Label at the bottom uses the two locally defined resources to color its background and foreground. In this case, however, these two resource brushes are referenced as static resources.

When the system colors change, the two brushes defined as local resources also change by getting new Color properties. The LinearGradientBrush and SolidColorBrush objects are not replaced, however. They are the same objects. The Label element references these two objects, so when these objects change, the background and foreground properties of the Label reflects the change in system colors.

If you change the Background and Foreground attributes of Label to DynamicResource, the program stops responding to changes in system colors! The problem is that DynamicResource is expecting the object referenced by the key to be recreated. The brush objects are not being recreated, so DynamicResource doesn't bother with updating the Foreground and Background properties. (At least, that's the best explanation I can come up with.)

It's possible to use the keys associated with system colors and other system settings in your own resource definitions. In that case, the local resource definitions override the system settings unless a local resource cannot be found. This stand-alone XAML file is a variation of the ResourceLookupDemo.xaml program from earlier in this chapter.

AnotherResourceLookupDemo.xaml

[View full width]

<!-- === =========== ============================================== AnotherResourceLookupDemo.xaml (c) 2006 by Charles Petzold ============================================= =============== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" Orientation="Horizontal"> <StackPanel> <StackPanel.Resources> <SolidColorBrush x:Key="{x:Static SystemColors .ActiveCaptionBrushKey}" Color="Red" /> </StackPanel.Resources> <Button HorizontalAlignment="Center" VerticalAlignment="Center" Margin="24" Foreground="{DynamicResource {x:Static SystemColors .ActiveCaptionBrushKey}}"> Button with Red text </Button> </StackPanel> <StackPanel> <Button HorizontalAlignment="Center" VerticalAlignment="Center" Margin="24" Foreground="{DynamicResource {x:Static SystemColors .ActiveCaptionBrushKey}}"> Button with Blue text </Button> </StackPanel> </StackPanel>



Only the first embedded StackPanel includes a Resources section, which defines a red brush using the key obtained from SystemColors.ActiveCaptionBrushKey. The Button within that StackPanel gets that red brush, but the other Button gets the active caption brush and changes when the system colors change.

As you use resources more, there may come a time when you'd like to share resources among multiple applications. This is particularly true if you've developed a collection of custom styles that give your company's applications their unique look and feel.

Resources that you want to share among multiple projects can be collected in XAML files with a root element of ResourceDictionary. Each resource is a child of that root element. Here's a possible resource dictionary that has only one resource, although it could contain many.

MyResources1.xaml

[View full width]

<!-- =============================================== MyResources1.xaml (c) 2006 by Charles Petzold ============================================= == --> <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx /2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <LinearGradientBrush x:Key="brushLinear"> <LinearGradientBrush.GradientStops> <GradientStop Color="Pink" Offset="0" /> <GradientStop Color="Aqua" Offset="1" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </ResourceDictionary>



Here's another resource dictionary that could also contain many resources but in this example has only one.

MyResources2.xaml.

[View full width]

<!-- =============================================== MyResources2.xaml (c) 2006 by Charles Petzold ============================================= == --> <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx /2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <RadialGradientBrush x:Key="brushRadial"> <RadialGradientBrush.GradientStops> <GradientStop Color="Pink" Offset="0" /> <GradientStop Color="Aqua" Offset="1" /> </RadialGradientBrush.GradientStops> </RadialGradientBrush> </ResourceDictionary>



You're now putting together a project named UseCommonResources, and you want to use the resources defined in MyResources1.xaml and MyResources2.xaml. You make those two files part of the project. They can have a Build Action of either Page or Resource. (Page is better because some preliminary processing occurs during compilation that turns the file from XAML into BAML.) In the application definition file for the project, you can have a Resources section with the syntax shown in the following file.

UseCommonResourcesApp.xaml

[View full width]

<!-- === ===================================================== UseCommonResourcesApp.xaml (c) 2006 by Charles Petzold ============================================= =========== --> <Application xmlns="http://schemas.microsoft.com/ winfx/2006/xaml/presentation" StartupUri="UseCommonResourcesWindow .xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="MyResources1.xaml" /> <ResourceDictionary Source="MyResources2.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>



Within the Resources section of the file is a ResourceDictionary element. ResourceDictionary defines a property named MergedDictionaries, which is a collection of other ResourceDictionary objects, and those objects are referenced by their file names. If you have only one resource dictionary, you can reference it in a single ResourceDictionary object without using the ResourceDictionary.MergedDictionaries property element.

The multiple resource dictionaries are truly merged. If you happen to use the same key in more than one file, the earlier resource with that key will be replaced by the later resource as the resource dictionaries are merged.

You can put the ResourceDictionary in the Resources section of a XAML file other than the application definition file, but the resources would be available only in that file and not throughout the application.

Finally, here's the Window element that makes use of the resources defined in the MyResources1.xaml and MyResources2.xaml files.

UseCommonResourcesWindow.xaml

[View full width]

<!-- === =========== ============================================= UseCommonResourcesWindow.xaml (c) 2006 by Charles Petzold ============================================= ============== --> <Window xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" Title="Use Common Resources" Background="{StaticResource brushLinear}"> <Button FontSize="96pt" HorizontalAlignment="Center" VerticalAlignment="Center" Background="{StaticResource brushRadial}"> Button </Button> </Window>



Approaching a new language like XAML can be accompanied by some anxiety. Has the language really been adequately defined so that we don't run into brick walls somewhere down the road? It's important for code to contain as little repetition as possible, and resources help achieve this goal. Not only can objects be defined once and reused throughout an application, but resources can also be stored in their own ResourceDictionary files to be reused in multiple applications.




Applications = Code + Markup. A Guide to the Microsoft Windows Presentation Foundation
Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation (Pro - Developer)
ISBN: 0735619573
EAN: 2147483647
Year: 2006
Pages: 72

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net