-
WPF应用程式:

资源(Resources)

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 vs. DynamicResource

目前为止的例子中,我使用了「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()」方法。

相反的情况则不成立,搜寻将不会反向往下遍历树状结构,所以如果你的资源定义在控制项或视窗,你不能从应用程式阶层开始寻找资源。