The .NET Framework has generic infrastructure for packaging and accessing resources—the noncode pieces of an application or component, such as bitmaps, fonts, audio/video files, and string tables. As with many other parts of WPF, WPF not only leverages the core .NET resources system but adds a little more support. WPF supports two distinct types of resources: binary resources and logical resources.
The first type—binary resources—is exactly what the rest of the .NET Framework considers to be resources. In WPF applications, these are typically traditional items like bitmaps. However, even compiled XAML gets stored as a binary resource behind the scenes. Binary resources can be packaged in three different ways:
• Embedded inside an assembly
• As loose files that are known to the application at compile time
• As loose files that might not be known to the application at compile time
An application’s binary resources are often put into two categories: localizable resources that must change depending on the current culture and language-neutral (or nonlocalizable) resources that don’t change based on culture. This section looks at the ways in which binary resources are defined, accessed, and localized.
The typical procedure for defining a binary resource consists of adding the file to a Visual Studio project and selecting the appropriate build action in the property grid, as shown in Figure 12.1 for an image called logo.jpg
.
Visual Studio supports several build actions for WPF applications, two of which are relevant for binary resources:
• Resource
—Embeds the resource into the assembly (or a culture-specific satellite assembly).
• Content
—Leaves the resource as a loose file but adds a custom attribute to the assembly (AssemblyAssociatedContentFile
) that records the existence and relative location of the file.
If you’re an MSBuild user editing a project file by hand, you can add such a file with the following syntax:
where BuildAction
is the name of the build action. A build action may include child elements that refine its behavior, as in the following example:
Embedded Resource
build action!The Resource
build action is confusingly similar to the EmbeddedResource
build action (Embedded Resource
in Visual Studio’s property grid). Both embed a binary resource inside an assembly, but the latter should be avoided in WPF projects. Whereas Resource
was added specifically for WPF, EmbeddedResource
predates WPF (and is used to embed binary resources in Windows Forms projects).
WPF’s APIs that reference resources via uniform resource identifiers (described in the next section) are designed for resources that use the build action Content
or Resource
only. This also means that resources embedded with the Content
or Resource
build action can be referenced easily from XAML, but resources embedded with the EmbeddedResource
build action cannot be (unless you write some custom code).
If you want to keep your resources as loose files, adding them to a project with a Content
build action is not required; you could simply put them at the appropriate location when the application runs and not worry about adding them to the project at all. This is not recommended, however, because it makes accessing the resources a bit less natural (as described in the next section). Still, sometimes using resources that aren’t known at compile time is inevitable, such as files that are dynamically generated at runtime.
Resources should be embedded (with the Resource
build action) if they are localizable, or if you feel the benefits of having a single binary file outweigh the benefits of having a loose file that can be easily replaced independently from the code. If neither of these is true, or if the content needs to be accessible directly from external entities as well (perhaps from HTML pages rendered inside the application), using the Content
build action is a good choice.
Whether binary resources are embedded with the Resource
build action, linked as loose files with the Content
build action, or left as loose files with no special treatment at compile time, WPF provides a mechanism for accessing them from code or XAML with a uniform resource identifier (URI). A type converter enables such URIs to be specified in XAML as simple strings with a few built-in shortcuts for common scenarios.
You can see this by examining the source code for the Photo Gallery application introduced in Chapter 7, “Structuring and Deploying an Application.” The following XAML snippet from Photo Gallery references several images that are included in the project with the Resource
build action:
Note that this same XAML works even if the .gif
files are given the Content
build action instead of Resource
(as long as the loose files are copied to the same directory as the executable when it runs). It does not work, however, if the loose .gif
files are not added to the project.
It often surprises people that compiled XAML, unlike loose XAML, can’t reference an arbitrary file in the current directory as follows:
If you require a resource to be loose and do not want to add it to your project, you have a few easy alternatives. One (unsatisfactory) alternative is to qualify the filename with its full path:
A better alternative is to use the following odd-looking syntax, described later in the “Accessing Resources at the Site of Origin” section:
The key to accessing binary resources, whether done with the Image
element or other elements, is understanding what URIs you can use to address a resource that could be embedded or loose. Table 12.1 summarizes the main options for URI strings in XAML. Note that not all of these options are available for partial-trust applications.
Note that the first two entries in Table 12.1 can work with both embedded and loose binary resources. This means that you can replace loose resources with embedded ones (or vice versa) without having to change your XAML.
The notion of using subfolders with embedded resources might sound a little odd, but it can be a nice way to organize embedded resources just as you would organize loose ones. For example, say that you put logo.jpg
in an images
folder in your Visual Studio project, using either of the following in the project file:
or
Then you can access it as follows, regardless of whether logo.jpg
physically resides as a loose file in an images
subfolder at runtime or if it’s simply embedded in the assembly:
The final four rows of Table 12.1 need a bit more explanation. The first two enable you to access binary resources embedded in another assembly, and the second two enable you to access binary resources at a special place known as a site of origin.
The ability to easily access binary resources embedded in another assembly is very handy (and gives you more options for updating resources without needing to replace the main executable), but the syntax is a little bizarre. As Table 12.1 implies, the syntax is
/AssemblyReference;Component/ResourceName
where AssemblyReference
identifies the specific assembly, but Component
is a keyword and must be used literally. ResourceName
is the filename (which can include subfolders).
AssemblyReference
can be the simple assembly display name, or it can optionally include other pieces of a .NET assembly’s identity: version number and public key token (if it’s a strong-named assembly). So, you have four options for AssemblyReference
:
• AssemblyName
• AssemblyName
;v
VersionNumber
(the v
prefix is required)
• AssemblyName
;
PublicKeyToken
• AssemblyName
;v
VersionNumber
;
PublicKeyToken
Although full-trust applications can hard-code a uniform resource locator (URL) or path for loose binary resources, taking advantage of the site of origin notion is a more maintainable approach. (In addition, it is required for partial-trust applications.) The site of origin gets resolved to different places at runtime, depending how the application is deployed:
• For a full-trust application installed with Windows Installer, the site of origin is the application’s root folder.
• For a full-trust ClickOnce application, the site of origin is the URL or UNC path from which the application was deployed.
• For a partial-trust XAML Browser Application (XBAP) or ClickOnce application, the site of origin is the URL or UNC path that hosts the application.
• For loose XAML pages viewed in a web browser, there is no site of origin. Attempting to use it throws an exception.
The syntax for taking advantage of the site of origin is even stranger than the syntax to reference resources embedded in another assembly! You must use the pack://siteOfOrigin:,,,/
prefix, followed by the resource name (which can contain subfolders). Note that siteOfOrigin
is a keyword to be used literally, not a placeholder for other text.
When creating URIs in C# for referencing resources, you aren’t able to use the XAML-specific shortcuts from Table 12.1. Instead, such URIs must be constructed with a fully qualified Pack URI or a fully qualified path/URL.
For example, the following code assigns an Image
’s Source
property to the contents of logo.jpg
:
This instantiates a System.Windows.Media.Imaging.BitmapImage
object (which works with popular image formats such as JPEG, PNG, GIF, and BMP), which ultimately derives from the abstract ImageSource
type (the type of the Source
property). The URI is represented by a System.Uri
object.
The use of pack://application:,,,/
works only with resources belonging to the current project marked as Resource
or Content
. To reference relative loose files with no relation to the project, the easiest approach is to use a siteOfOrigin
-based URI.
If an application contains some binary resources that are specific to certain cultures, you can partition them into satellite assemblies (one per culture) that get loaded automatically, when appropriate. If you’re doing this, then you likely have strings in your user interface that you need to localize as well. LocBaml, a sample tool in the Windows SDK, makes it easy to manage the localization of strings and other items without having to rip them out of XAML and manually apply a level of indirection. This section walks through the basics steps to get started with LocBaml and satellite assemblies.
To specify a default culture for resources and automatically build an appropriate satellite assembly, you must add a UICulture
element to the project file. Visual Studio doesn’t have a means to set this within its environment, so you can open the project file in your favorite text editor instead.
You can open a raw project file without leaving Visual Studio if you right-click and unload it from the current solution first. After it’s unloaded, right-click the project again and select Edit from the context menu.
The UICulture
element should be added under any or all PropertyGroup
elements corresponding to the build configurations you want to affect (Debug, Release, and so on), or to a property group unrelated to build configuration so it automatically applies to all of them. This setting should look as follows for a default culture of American English:
If you rebuild your project with this setting in place, you’ll find an en-US
folder alongside your assembly, containing the satellite assembly named AssemblyName
.resources.dll
.
You should also mark your assembly with the assembly-level NeutralResourcesLanguage
custom attribute with a value matching your default UICulture
setting, as follows:
The next step is to apply a Uid
directive from the XAML language namespace (x:Uid
) to every object element that needs to be localized. The value of each directive should be a unique identifier.
This would be extremely tedious to do by hand, but it fortunately can be done automatically by invoking MSBuild from a command prompt, as follows:
msbuild /t:updateuid ProjectName.csproj
Running this gives every object element in every XAML file in the project an x:Uid
directive with a unique value. You can add this MSBuild task inside your project before the Build task, although this might produce too much noise if you rebuild often.
After compiling a project that has been enhanced with Uid
s, you can run the LocBaml tool from the Windows SDK on a .resources
file generated by the build process (found in the objdebug
directory), as follows:
LocBaml /parse ProjectName.g.en-US.resources /out:en-US.csv
This generates a simple .csv
text file containing all the property values you should need to localize. You can edit the contents of this file so it correctly corresponds to a new culture. (There’s no magic in this part of localization!) If you save the file, you can then use LocBaml in the reverse direction to generate a new satellite assembly from the .csv
file! For example, if you changed the contents of the .csv
file to match the French Canadian culture, you could save the file as fr-CA.csv
and then run LocBaml as follows:
LocBaml /generate ProjectName.resources.dll /trans:fr-CA.csv /cul:fr-CA
This new satellite assembly needs to be copied to a folder alongside the main assembly with a name that matches the culture (fr-CA
in this case).
To test a different culture, you can set System.Threading.Thread.CurrentThread.CurrentUICulture
(and System.Threading.Thread.CurrentThread.CurrentCulture
) to an instance of the desired CultureInfo
.
The second type of resources is a mechanism first introduced by WPF and supported by both WPF and Silverlight. In this chapter, these resources are called logical resources for lack of a better term, but mostly the book refers to them as resources in contrast to the binary resources just covered. (You might be tempted to call them XAML resources, but as with almost everything else in XAML, you can create and use them entirely in procedural code.)
These logical resources are arbitrary .NET objects stored (and named) in an element’s Resources
property, typically meant to be shared by multiple child elements. The FrameworkElement
and FrameworkContentElement
base classes both have a Resources
property (of type System.Windows.ResourceDictionary
), so most WPF classes you’ll encounter have such a property. These logical resources are often styles (covered in Chapter 14, “Styles, Templates, Skins, and Themes”) or data providers (covered in Chapter 13, “Data Binding”). But this chapter demonstrates logical resources by storing some simple Brush
es.
Listing 12.1 contains a simple Window
with a row of Button
s along the bottom, similar to ones from the Photo Gallery user interface. It demonstrates a brute-force way to apply a custom Brush
to each Button
’s (and the Window
’s) Background
, as well as each Button
’s BorderBrush
. Figure 12.2 shows the result.
Alternatively, you could organize the yellow and red Brush
es as logical resources for the Window
and apply them to individual elements as resource references. This is a nice way to separate and consolidate the style information, much like using Cascading Style Sheets (CSS) to control colors and styles in a webpage rather than hard-coding them on individual elements. The sharing of objects enabled by the logical resources scheme can also help you consume significantly less memory, depending on the complexity of the objects. Listing 12.2 is an update to Listing 12.1, using logical resources for the two Brush
es.
The definition of resources and the x:Key
syntax should look familiar, from when ResourceDictionary
was introduced in Chapter 2, “XAML Demystified.” Applying the resource to elements uses the StaticResource
markup extension (short for System.Windows.StaticResourceExtension
). This is applied to Window.Background
with property element syntax, and to Button.Background
and Button.BorderBrush
with property attribute syntax. Because both resources in this example are Brush
es, they can be applied anywhere a Brush
is expected.
Because simple yellow and red Brush
es are still used in Listing 12.2, the result looks identical to Figure 12.2. But now, you can replace the Brush
es in one spot and leave the rest of the XAML alone (as long as you use the same keys in the resource dictionary). For example, replacing the backgroundBrush
resource with the following linear gradient produces the result in Figure 12.3:
The StaticResource
markup extension accepts a single parameter representing the key to the item in a resource dictionary. But that item doesn’t have to be inside the current element’s resource dictionary. It could be in any logical parent’s collection, or even in application-level or system-level resource dictionaries.
The markup extension class implements the ability to walk the logical tree to find the item. It first checks the current element’s Resources
collection (its resource dictionary). If the item is not found, it checks the parent element, its parent, and so on until it reaches the root element. At that point, it checks the Resources
collection on the Application
object. If it is not found there, it checks a collection of theme resources, a concept covered in Chapter 14. If it is not found there, it finally checks a system collection (which contains system-defined fonts, colors, and other settings). If the item is in none of these collections, it throws an InvalidOperationException
.
Recall from Chapter 7 that a WPF application may have multiple UI threads. In such an application, application-level resources will be accessed directly by each of these threads. To make this work, such resources must either be Freezable
s that are frozen, or marked with x:Shared=false
, an attribute described in the upcoming “Resources Without Sharing” section.
Because of this behavior, resources are typically stored in the root element’s resource dictionary or in the application-level dictionary for maximum sharing potential. Note that although each individual resource dictionary requires unique keys, the same key can be used in multiple collections. The one “closest” to the element accessing the resource will win because of the way the tree gets walked.
WPF provides two ways to access a logical resource:
• Statically with StaticResource
, meaning that the resource is applied only once (the first time it’s needed)
• Dynamically with DynamicResource
, meaning that the resource is reapplied every time it changes
The DynamicResource
markup extension (System.Windows.DynamicResourceExtension
) implements the ability to walk the logical tree just like StaticResource
does, so DynamicResource
can often be used wherever StaticResource
is used to get the same effect. Nothing about the resource declarations themselves make them suited for one versus the other; choosing StaticResource
or DynamicResource
is mostly about deciding whether you want consumers of the resource to see updates. In fact, you could even mix and match StaticResource
and DynamicResource
with the same resource key, although that would be a strange thing to do.
The main difference between StaticResource
and DynamicResource
is that any subsequent updates to the resource are reflected only to those elements that use DynamicResource
. Such updates can be done in your own code (changing a yellow Brush
to blue, for example) or they can be done by a user changing system settings.
StaticResource
and DynamicResource
have different performance characteristics. On the one hand, using DynamicResource
requires more overhead than StaticResource
because of the extra tracking. On the other hand, the use of DynamicResource
can potentially improve load time. StaticResource
references are always loaded when the Window
or Page
loads, whereas a DynamicResource
reference is not loaded until it is actually used.
In addition, DynamicResource
can only be used to set dependency property values, whereas StaticResource
can be used just about anywhere. For example, you could use StaticResource
as an element to abstract away entire controls! This Window
:
is equivalent to this Window
:
Using elements such as Image
as a resource might be an interesting way to factor XAML, but it doesn’t allow you to share the object. Image
can have only one parent because it derives from Visual
(therefore participating in the logical and visual trees), so any attempt to use the same object as a resource more than once fails. For example, pasting a second but identical StaticResource
element in the preceding XAML snippet produces an exception with the message “Specified Visual is already a child of another Visual or the root of a CompositionTarget.
”
There’s one more (and subtle) difference between static and dynamic resource access. When using StaticResource
in XAML, forward references aren’t supported. In other words, any uses of the resource must appear after it is declared in the XAML file. This means you can’t use StaticResource
with property attribute syntax if the resource is defined on the same element (because the resource necessarily appears afterward)! DynamicResource
does not have this limitation.
This forward reference rule is the reason that the Window
in Listing 12.2 uses property element syntax to set Background
. By doing so, it ensures that the resource is defined before it is used.
Although DynamicResource
could be used the same way, you can also use it via property attribute syntax in this case because it doesn’t matter that the resource is referenced before it is defined:
By default, when a resource is applied in multiple places, the same object instance is used everywhere. This is usually the desired behavior. However, you can mark items in a compiled resource dictionary with x:Shared="False"
to make each request for that resource produce a distinct instance of the object that can be modified independently of the others.
One case where this behavior can be interesting is the previous example of using an entire Image
(or any other Visual
-derived object) as a resource. Such a resource can be applied only once in an element tree because each application is the same instance. But setting x:Shared="False"
changes this behavior, enabling the resource to be applied multiple times as independent objects. This could be done as follows:
Note that x:Shared
can be used only in a compiled XAML file. Its use in loose XAML files is not supported.
So far, this chapter has examined how to define and apply logical resources in XAML, but it hasn’t yet looked at what it means to do the same things in procedural code. Fortunately, defining resources in code is straightforward. The two SolidColorBrush
resources used in Listing 12.2 can be defined as follows in C#, assuming a Window
called window
:
Applying resources in code is a different story, however. Because StaticResource
and DynamicResource
are markup extensions, the equivalent C# code to find and apply resources is not obvious.
For StaticResource
, you can get the equivalent behavior by setting an element’s property to the result from its FindResource
method (inherited from FrameworkElement
or FrameworkContentElement
).
So, the following Button
(similar to one declared in Listing 12.2):
is equivalent to the following C# code (assuming an appropriate StackPanel
variable named stackPanel
for containing the Button
):
FindResource
throws an exception when the resource cannot be found, but you can alternatively call TryFindResource
, which returns null
when the lookup fails.
For DynamicResource
, a call to an element’s SetResourceReference
(also inherited from FrameworkElement
or FrameworkContentElement
) does the trick of setting up the updatable binding with the dependency property.
Therefore, replacing both StaticResource
references with DynamicResource
:
is equivalent to using the following C# code:
This works as long as the Button
is eventually added to the element tree as a descendant of the Window
(where the resources are defined). Unlike the StaticResource
case, such placement in the tree does not need to happen before referencing each resource.
The forward reference rule with StaticResource
also applies to procedural code. A call to FindResource
or TryFindResource
fails if you call it before adding the resource to an appropriate resource dictionary with the appropriate key. SetResourceReference
, on the other hand, can be called before the resource has been added.
One obvious place where it’s appropriate to use DynamicResource
is with system settings encapsulated by static properties on three classes in the System.Windows
namespace: SystemColors
, SystemFonts
, and SystemParameters
. That’s because a user can change the settings via Control Panel while your application is running.
The SystemColors
, SystemFonts
, and SystemParameters
classes define their properties in pairs—a property for each actual value and a corresponding property that serves as the resource key to be used for lookups. Each resource key property is given a Key
suffix by convention. For example, SystemColors
contains properties of type Brush
called WindowBrush
and WindowTextBrush
along with properties of type ResourceKey
called WindowBrush
Key
and WindowTextBrush
Key
.
Table 12.2 demonstrates the various ways you might try to set a Button
’s background to the system’s currently defined “window color.” The second approach is what I see people do most commonly, but only the last approach is completely correct.
Of all the WPF features covered in this part of the book, the support for resources is the one that is practically impossible to live without. It’s hard to build a professional-looking application without at least an icon and a few images!
But using resources is about much more than just making an application or a control look (or sound, if you’re using audio resources) a little better. It’s a fundamental piece of enabling software to be localized into different languages. It also enables higher productivity for developing software because the logical resources support enables you to consolidate information that might otherwise be duplicated, and even factor XAML files into more manageable chunks. The most fun—and perhaps most important—application of logical resources is their use with objects such as styles and templates, covered in Chapter 14.