- WPF资源的优点
WPF资源系统是一种保管一系列有用对象的简单方法,从而可以更容易地重用这些对象,它主要有以下优点:- 高效,通过资源可以定义一个对象,并在标记中的多个地方重用,这会使代码变的更加精简,并且更加高效。
- 可维护性,通过资源可以使用低级的格式化细节,并将它们移到方便对其进行修改的*位置。在XAML中创建资源,相当于在代码中创建常量。
- 适应性,一旦特定的信息和应用程序的其它部分相分离,并且放置到资源部分中,就可以动态地修改它。
-
资源集合
每个元素都是有一个Resources属性(它是在FrameworkElement类中定义的),它存储了一个资源字典集合(它是Resource Dictionary类的实例),资源集合可以包含任意类型的对象,并根据字符串编写索引。例如,以下示例显示了如何定义一个画刷资源:<Window.Resources>
<ImageBrush x:Key="TileBrush" TileMode="Tile"Viewport="0 0 32 32" ImageSource="images/happyface.jpg"/>
</Window.Resources>在以上代码中使用了Key特性(以名称空间前缀x:开头,这会将该画刷放置到XAMl名称空间中,而不是WPF名称空间中),该特性指定了在WIndow.Resources.Collection集合中编写画刷索引的名称,当需要检索资源时,只要使用相同的的名称,就可以使用需要的任何内容。可以在资源部分中实例化任何.Net类,只要该类是XAMl友好的,友好的就是指该类有公有的无参构造函数和可写的属性。以下是一个使用该资源的按钮:
<Button Grid.Row="0" Margin="3" Background="{StaticResource TileBrush}"></Button>
<Button Grid.Row="1" Margin="3" Background="{DynamicResource TileBrush}"></Button> - 静态资源和动态资源的区别
静态资源只从资源集合中获取对象一次,而动态资源在每次需要对象时都会重新从资源集合中查找对象,这意味着可以在同一个键下放置一个全新对象,并且动态资源会应用该变化。下面的按钮事件处理程序使用全新的纯色画刷替换了以上定义的图像画刷TileBrush:private void Button_Click(object sender, RoutedEventArgs e)
{
this.Resources["TileBrush"] = new SolidColorBrush(Colors.AliceBlue);
}动态资源会立即应用该变化,而静态资源不知道它的画刷已经在Resources集合中被其它内容替换了,它仍然使用原来的ImageBrush。但通常不需要使用动态资源,因为动态资源的开销比较大,使用静态资源也能够完美的工作,以下是需要使用动态资源的一般原则:
- 资源具有依赖于系统设置的属性(如当前Windows操作系统的颜色或字体)。
- 计划通过编程替换资源对象(如实现几类动态皮肤特性)。
- 非共享资源
通常,当在多个地方使用某种资源时,使用的是同一个对象,这种行为称为共享,然而,也可能希望告诉解析器在每次使用时创建单独的对象实例,为了关闭共享行为,需要使用Shared特性,如下所示,但很少有理由需要使用非共享的资源:<ImageBrush x:Key="TileBrush" x:Shared="False" Viewport="0 0 32 32" ImageSource="images/happyface.jpg" Opacity="0.3"></ImageBrush>
- 通过代码访问资源
控件能够检索资源,而不需要知道定义资源的确切位置,当尝试为Background属性指定画刷时,WPF会在按钮的资源集合中检索名为TileBrush的资源,然后检查包含StackPanel的资源集合,接下来检查窗口。可以使用FrameworkElement.FindResource()方法以相同的方式查找资源, 如:private void Button_Click(object sender, RoutedEventArgs e)
{
ImageBrush brush = (ImageBrush)((Button)sender).FindResource("TileBrush");
}可以使用TryFindResource()方法替代FindResource()方法,如果找不到资源,它会返回一个空引用,而不是抛出一个异常。
- 应用程序资源
窗口不是查找资源的最后一站,如果在控件或容器中找不到指定的资源,WPF会继续检查为应用程序定义的资源集合,在VS中,这些资源定义在App.xaml文件的标记中:<Application x:Uid="Application_1" x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ImageBrush x:Key="TileBrush" TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 32 32" ImageSource="images/happyface.jpg"/>
</Application.Resources>
</Application>定义应用程序资源就是为了在多个窗口中使用资源,但在创建应用程序资源时,需要考虑复杂性和重用性,添加应用程序资源可以得到好的重用性,但是也会增加复杂性,因为没有明确哪些窗口使用给定的资源,一个正确的指导原则是,如果对象需要被广泛重用(例如,在许多窗口中),可以使用应用程序资源,如果只是在两三个窗口中使用,就可以考虑在每个窗口中定义资源。
- 系统资源
查找资源时应用程序资源还不是最后一站,如果在应用程序中没有找到所需要的资源,元素还会继续查找系统资源。动态资源主要用于辅助应用程序对系统环境设置的变化做出响应,但如何检索系统环境设置并在代码中使用它们?为此需要三个类:SystemColors、SystemFonts和SystemParameters,这些类位于System.Windows名称空间中,其中SystemParameters类包装了大量的设置列表,描述了各种屏幕元素的标准尺寸、键盘、鼠标设置及各种图形效果(如阴影)是否已经打开。这些类通过它们的静态属性提供了它们所有的细节,如:
lb.Foreground = new SolidColorBrush(SystemColors.WindowTextColor);
或者为了提高效率,可以使用现成的画刷属性:
lb.Foreground = SystemColors.WindowTextBrush;
在XAML中可以使用静态标记扩展访问静态的SystemColors.WindowTextBrush来为标签设置前景色:
<Label Foreground="{x:Static SystemColors.WindowTextBrush}">My Test</Label>
上面的示例没有使用资源,当解析窗口并且创建标签时,会根据当前窗口文本颜色的快照创建画册,如果在应用程序运行时改变了Windows颜色,Label控件不会更新它的前景色,具有这种行为的应用程序是不太合理的。为了解决这个问题,需要将Label的Foreground属性设置为包装了系统资源的DynamicResource对象,幸运的是,WPF为所有SystemXXX类都提供了用于返回ResourceKey对象引用的补充属性集,使用这些引用可以从系统资源中提取资源,这些属性与直接返回对象的变通属性同名,后面加上单词Key。如SystemColors.WindowTextBrush的资源键是SystemColors.WindowTextBrushKey。下面的代码显示了如何使用来自SystemXXX类的资源:
<Label Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}">My Test</Label>
- 资源字典
如果希望在多个项目间共享资源,可以创建一个资源字典,它是一个简单的XAML文件,该文档除了存储希望使用的资源外,不做其它任何事情。- 创建资源字典
如下是一个创建资源字典的示例:<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ImageBrush x:Key="TileBrush" TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 32 32" ImageSource="sadface.jpg"/>
</ResourceDictionary> - 使用资源字典
要使用资源字典,需将其添加到应用程序中某些位置的资源集合中,例如:<Application x:Uid="Application_1" x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ResourceDictionaryTest.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
<ImageBrush x:Key="TileBrush" TileMode="Tile" Viewport="0 0 32 32" ImageSource="images/happyface.jpg"/>
</ResourceDictionary>
</Application.Resources>
</Application>ResourceDictionary.MergedDictionaries集合是一个ResourceDictionary对象的集合,使用它可以把多个资源字典文件进行合并。如果希望添加自己的资源,并合并到资源字典中,只需要在MergedDictionaries部分之前或之后放置资源。
使用资源字典的一个原因是定义一个或多个可以重用的应用程序“皮肤”,可将皮肤应用到控件上,另一个原因是存储需要被本地化的内容(如错误消息字符串)。 -
在程序集之间共享资源
如果希望在多个应用程序之间共享资源字典,可以复制并分发包含资源字典的XAML文件,但是这样不能对版本进行控制。另一种更好的方法是将资源字典编译到一个单独的类库程序集中,并分发组件。在应用程序中如何提取定义在其它程序集中的资源,一种方法是使用代码创建合适的ResourceDictionary对象,例如,有一个类库程序集ResourceLibrary中有一个名为generic.xaml的资源字典,可以使用下面的代码手动创建该资源字典,且手动从创建的资源字典对象的资源集合中检索所需要的资源:
ResourceDictionary resourceDic = new ResourceDictionary();
resourceDic.Source = new Uri("ResourceLibrary;component/generic.xaml", UriKind.Relative);
btnTest.Background = (Brush)resourceDic["TileBrush"];另一种方法是使用ComponentResourceKey标记扩展,使用它为资源创建键名,来告知WPF在程序集之间共享资源。必须将资源字典放到generic.xaml文件中,并且该文件必须被放到应用程序文件夹中的Themes子文件夹中,在generic.xaml文件夹中的资源被认为是默认主题的一部分,并且它们总是可用的。如果有大量的资源,并希望以合理的方式进行组织,可以创建单独的资源字典文件,但要确保将这些资源字典合并到generic.xaml文件中,从而为共享做好准备。下一步是为存储在ResourceLibrary程序集中希望共享的资源创建键名。当使用ComponentResourceKey时,需要提供两部分信息:类库程序集中类的引用和描述性的资源ID,类引用是WPF允许和其它程序集共享资源的关键部分,当使用资源时,需要提供相同的类引用和资源ID。这个类的外观并不重要,它不需要包含代码,定义该类型的程序集就是ComponentResourceKey将要从中查找资源的程序集,如下示例使用了一个命名为CustomResources的类,它没有包含任何代码:
public class CustomResources {}
现在可以使用这个类和资源ID创建键名了:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ResourceLibrary">
<ImageBrush x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:CustomResources},ResourceId=SadTileBrush}"
TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 32 32" ImageSource="ResourceLibrary;component/sadface.jpg"/>
</ResourceDictionary>在以上代码中,ImageSource属性使用一个相对URI来明确指示图像是ResourceLibrary组件的一部分。
现在已经创建了资源字典,可以在另一个应用程序中使用它了,首先,需要为类库程序集声明名称空间且指定一个前缀:<Window x:Class="WpfApplication1.ResourcesDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:res="clr-namespace:ResourceLibrary;assembly=ResourceLibrary"
Title="ResourcesDemo" Height="300" Width="300">
<Grid>
<Button Background="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type res:CustomResources},
ResourceId=SadTileBrush}}">Test</Button>
</Grid>
</Window>当使用ComponentResourceKey时,必须使用动态资源,而不能使用静态资源。以上的键名定义太复杂,可以定义一个静态属性,让它返回需要使用的正确的ComponentResourceKey。通常,在组件的类中定义该属性,如下所示:
public class CustomResources
{
public static ComponentResourceKey SadTileBrushKey
{
get { return new ComponentResourceKey(typeof(CustomResources), "SadTileBrush"); }
}
}现在可以使用Static标记扩展访问该属性并应用资源了,而不需要在标记中使用很长的ComponentResourceKey:
<Button Background="{DynamicResource {x:Static res:CustomResources.SadTileBrushKey}}"></Button>
这种方便的快捷方法在本质上和前面的SystemXXX类使用的是相同的技术,例如,当检索SystemColors.WindowTextBrushKey时,所接收的也是正确的资源键对象,唯一的区别是,它是私有SystemRourceKey对象,而不是ComponentResourceKey的一个实例。这两个类都继承自ResourceKey对象。
- 创建资源字典