WPF採用了一项非常便利的概念:储存资料为资源的能力,无论是区域性的支援一个控制项、区域性的支援整个视窗或是全域性的支援整个应用程式。能储存的资料可以是你想要的任何东西,从实际资讯到一个WPF控制项的阶层。这允许你将资料存放于一个地方,再从其他数个地方使用它,非常地实用。
这项概念常用在风格(styles)及模板上(templates),我们会在之后的教学中讨论。然而如同将在这个章节说明的,你也可以把它用在许多其他东西上。让我透过一个简单的范例来展示:
<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>
各个资源将透过「x:Key」属性给予一个键值,这允许你在应用程式的其他地方透过该键值结合「StaticResource」之标记延伸来参照该资源。在这个例子中,我只储存了一个间单的字串,之后使用在两个不同的「TextBlock」控制项中。
目前为止的例子中,我使用了「StaticResource」之标记延伸来参照资源,然而有另外一个以「DynamicResource」形式的选择。
主要的差异即是静态资源仅会在XAML载入的时间点被设定一次,如果这个资源之后被改变,这项改变将不会反映到你使用「StaticResource」的地方。
另一方面,一个「DynamicResource」会在它实际需要时设定一次,并且当资源改变时再次设定。可以想成「繫结一个静态值」与「繫结一个监视这个值并且每当该值改变时将它传送给你的函式」的差异,这并不是它实际上运作的方式,不过可以让你比较容易了解何时该使用哪个。动态资源也允许你使用设计阶段尚未存在的资源,例如你在后置程式码中于应用程式启动时加入的资源。
共用一个简单字串非常的容易,但你可以做的更多。在下一个例子中,我同时会储存一个完整的字串阵列搭配一个用于背景的渐层笔刷,这应该能非常好的让你了解你可以透过资源做到多少事:
<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>
这次我们加入了一些额外的资源,所以我们的视窗现在包含了一个简单字串、一个字串阵列以及一个「LinearGradientBrush」。字串使用在标籤(label)上,而字串阵列作为「ComboBox」控制项的项目,渐层笔刷则用于整个视窗的背景。如你所见,几乎任何东西都可以储存为资源。
目前我们将资源储存在视窗阶层,也就是你可以在该视窗的任何地方存取这些资源。
如果你需要一个资源仅仅用在特定的控制项上,你可以藉由将资源加在该特定控制项内而非视窗内,来让资源更区域性。使用方法完全一样,唯一的差别是你现在只能在你加入资源的控制项范围内存取该资源。
<StackPanel Margin="10">
<StackPanel.Resources>
<sys:String x:Key="ComboBoxTitle">Items:</sys:String>
</StackPanel.Resources>
<Label Content="{StaticResource ComboBoxTitle}" />
</StackPanel>
在这个例子中,我们将资源加在一个「StackPanel」中并在它的子控制项「Label」中使用该资源,而其他在「StackPanel」中的控制项也能使用该资源,就如同该子控制项的子项有权存取它一样。反之,在该特定「StackPanel」外的控制项则无法存取。
如果你需要从多个视窗存取资源也是可行的,在App.xaml档案中可以容纳像是视窗及任何其他种类的WPF控制项资源,而当你将这些资源储存于App.xaml,专案中的所有视窗及使用者控制项都能够全域地存取它们,使用方法也和在视窗内储存及使用时完全一样。
<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>
使用时也一样:WPF会自动地从区域控制项到视窗再到App.xaml向上寻找指定的资源。
<Label Content="{StaticResource ComboBoxTitle}" />
目前为止,我们已经直接从XAML透过标记延伸存取所有我们的资源,然而你当然也可以从后置程式码中存取你的资源,这在很多情况下非常实用。在前面的例子中,我们看到了我们如何将资源储存在多个不同的地方,所以在这个例子中,我们将从后置程式码存取分别储存在不同范围内的三个不同的资源:
App.xaml:
<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>
视窗:
<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>
后置程式码:
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());
}
}
}
诚如你所见,我们储存了三个不同的「Hello, world!」讯息:一个在App.xaml中、另一个在视窗内、而最后一个是主面板的区域资源。介面则由一个按钮及一个「ListBox」组成。
在后置程式码中,我们处理了按钮的点击事件,如同截图所示,我们将各个文字字串加入到「ListBox」中。我们使用「FindResource()」方法,这个方法将会将资源回传为一个物件(如果有找到),接著我们使用众所皆知的方法「ToString()」将其转换为字串。
请注意我们如何对不同的范围使用「FindResource()」方法:首先对于面板(panel)、再来对于视窗、最后对于当前应用程式物件。在我们已知的位置找寻资源是很合理的,不过就如同前面提到的,如果资源没有找到,搜寻程序将会往更高的阶层找寻。所以原则上在上面三个情况下,由于在找不到的时候程序都会继续向上对于视窗阶层、接著对于应用程式阶层找寻,因此我们可以都对于面板使用「FindResource()」方法。
相反的情况则不成立,搜寻将不会反向往下遍历树状结构,所以如果你的资源定义在控制项或视窗,你不能从应用程式阶层开始寻找资源。