WPF introduces a very convenient concept: storing data as resources can be used locally for controls, locally for the whole window, or globally for the whole application. Data can be almost anything you want, from actual information to the hierarchy of WPF controls. This allows you to put data in one place and then use it from one or several other places, which is very useful.
This concept is often used for styles and templates, which we will discuss later in this tutorial, but as this chapter will illustrate, you can also use it for many other things. A simple example to demonstrate it:
<Window x:Class="WpfTutorialSamples.WPF_Application.ResourceSample" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" Title="ResourceSample" Height="150" Width="350"> <Window.Resources> <sys:String x:Key="strHelloWorld">Hello, world!</sys:String> </Window.Resources> <StackPanel Margin="10"> <TextBlock Text="{StaticResource strHelloWorld}" FontSize="56" /> <TextBlock>Just another "<TextBlock Text="{StaticResource strHelloWorld}" />" example, but with resources!</TextBlock> </StackPanel> </Window>
The resource is given a key, using the x:Key attribute, which allows you to reference it from other parts of the application by using this key in conjunction with the StaticResource tag extension. In this example, I only store a simple string, and then I start from two different strings Use it in a TextBlock control.
Static resources and dynamic resources
In the example so far, I have used the StaticResource tag extension to reference resources. However, there is an alternative in the form of dynamic resource.
The main difference is that static resources are resolved only once when XAML is loaded. If you subsequently change the resource, the change will not be reflected where you use StaticResource.
On the other hand, dynamic resources will be parsed when actually needed. If the resources change, they will be parsed again. Think of it as binding to a static value and binding to a function that monitors this value and sends it to you every time it changes - this is not exactly how it works, but it should give you a better understanding of when and what to use. Dynamic resources also allow you to use resources that do not even exist at design time, for example, if you add them from code behind during application startup.
More resource types
Sharing a simple string is easy, but you can do more. In the next example, I will also store a complete array of strings and a gradient brush for the background.
<Window x:Class="WpfTutorialSamples.WPF_Application.ExtendedResourceSample" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" Title="ExtendedResourceSample" Height="160" Width="300" Background="{DynamicResource WindowBackgroundBrush}"> <Window.Resources> <sys:String x:Key="ComboBoxTitle">Items:</sys:String> <x:Array x:Key="ComboBoxItems" Type="sys:String"> <sys:String>Item #1</sys:String> <sys:String>Item #2</sys:String> <sys:String>Item #3</sys:String> </x:Array> <LinearGradientBrush x:Key="WindowBackgroundBrush"> <GradientStop Offset="0" Color="Silver"/> <GradientStop Offset="1" Color="Gray"/> </LinearGradientBrush> </Window.Resources> <StackPanel Margin="10"> <Label Content="{StaticResource ComboBoxTitle}" /> <ComboBox ItemsSource="{StaticResource ComboBoxItems}" /> </StackPanel> </Window>
This time, we added some additional resources, so our Window now contains a simple string, a string array and a LinearGradientBrush. The string is used as a label, the string array is used as an item of the ComboBox control, and the gradient brush is used as the background of the whole Window. So, as you can see, almost anything can be stored as a resource.
Local and application wide resources
Currently, we store resources at the window level, which means you can access them from the entire window.
If you only need a given resource for a specific control, you can make it more localized by adding it to this specific control instead of a window. It works in exactly the same way, except that you can only access it from the scope of the control where it is placed:
<StackPanel Margin="10"> <StackPanel.Resources> <sys:String x:Key="ComboBoxTitle">Items:</sys:String> </StackPanel.Resources> <Label Content="{StaticResource ComboBoxTitle}" /> </StackPanel>
In this case, we add the resource to the StackPanel and use it from its child control Label. Other controls in the StackPanel can also use it, just as the child controls of these child controls can access it. However, controls outside this particular StackPanel will not be able to access it.
This is also possible if you need to be able to access resources from multiple windows. App.xaml file can contain resources like windows and any type of WPF control. When you store them in app.xaml, they can access the windows and user controls of all projects in the whole range. It works exactly the same as when it is stored and used from Window:
<Application x:Class="WpfTutorialSamples.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" StartupUri="WPF application/ExtendedResourceSample.xaml"> <Application.Resources> <sys:String x:Key="ComboBoxTitle">Items:</sys:String> </Application.Resources> </Application>
Code behind resources
So far, we have accessed all our resources directly from XAML using tag extensions. However, of course, you can also access your resources from code behind, which is useful in many cases. In the previous example, we saw how to store resources in multiple different locations. Therefore, in this example, we will access three different resources from code hiding, each of which is stored in a different range:
<Application x:Class="WpfTutorialSamples.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" StartupUri="WPF application/ResourcesFromCodeBehindSample.xaml"> <Application.Resources> <sys:String x:Key="strApp">Hello, Application world!</sys:String> </Application.Resources> </Application>
Form:
<Window x:Class="WpfTutorialSamples.WPF_Application.ResourcesFromCodeBehindSample" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" Title="ResourcesFromCodeBehindSample" Height="175" Width="250"> <Window.Resources> <sys:String x:Key="strWindow">Hello, Window world!</sys:String> </Window.Resources> <DockPanel Margin="10" Name="pnlMain"> <DockPanel.Resources> <sys:String x:Key="strPanel">Hello, Panel world!</sys:String> </DockPanel.Resources> <WrapPanel DockPanel.Dock="Top" HorizontalAlignment="Center" Margin="10"> <Button Name="btnClickMe" Click="btnClickMe_Click">Click me!</Button> </WrapPanel> <ListBox Name="lbResult" /> </DockPanel> </Window>
Code behind:
using System; using System.Windows; namespace WpfTutorialSamples.WPF_Application { public partial class ResourcesFromCodeBehindSample : Window { public ResourcesFromCodeBehindSample() { InitializeComponent(); } private void btnClickMe_Click(object sender, RoutedEventArgs e) { lbResult.Items.Add(pnlMain.FindResource("strPanel").ToString()); lbResult.Items.Add(this.FindResource("strWindow").ToString()); lbResult.Items.Add(Application.Current.FindResource("strApp").ToString()); } } }
Therefore, we store three different "Hello, world!" messages: one in App.xaml, one in the window, and one locally for the main panel. The interface consists of a button and a list box.
In code behind, we handle the button click event, in which we add each text string to the ListBox, as shown in the screenshot. We use FindResource() method, which returns the resource as an object (if found), and then we use the ToString() method to convert it to a string we know.
Notice how we use the FindResource() method in different scopes - first on the panel, then on the window, and then on the current On the application object. It is meaningful to look for resources where we know, but as already mentioned, if the resources are not found, the search will be carried out upward in the hierarchy, so in principle, we can use the FindResource() method on the panel in all three cases, because it will continue to the window and then to the application level, if not found.
The reverse is different - search does not navigate down the tree, so you cannot start looking for a resource at the application level if it is already defined locally for a control or window.