《深入浅出WPF》——资源学习

24 篇文章 20 订阅

一、前言

我们把有用的东西称为资源。“兵马未动,粮草先行”——程序中的各种数据就是算法的原料和粮草。程序中可以存放数据的地方有许多,可以放在数据库里、可以存储在变量里。介于数据库存储和变量存储之间,我们还可以把数据存储在程序主体之外的文件里。外部文件与程序主体分离,这就有可能丢失或损坏,为了避免丢失或损坏,编译器允许我们把外部文件编译进程序主体、成为程序主体不可分割的一部分,这就是传统意义上的程序资源(也称为二进制资源)

WPF不但支持程序级的传统资源,同时还推出了独具特色的对象级资源,每个界面元素都可以携带自己的资源并可被自己的子级元素共享。比如各种模板(Template)、程序样式(Style)和主题(Themes)就经常放在对象级资源里。这样一来,在WPF程序中数据就分为四个等级存储了:数据库里的数据相当于放在仓库里,资源文件里的数据就相当于放在旅行箱里,WPF对象资源里的数据相当于放在随身携带的背包里,变量中的数据相当于拿在手里。


二、资源

1. WPF对象级资源的定义与查找

每个WPF的界面元素都具有一个名为Resources的属性,这个属性继承自FrameworkElement类,其类型为ResourceDictionary。ResourceDictionary能够以“键-值”对的形式存储资源,当需要使用某个资源时,使用“键-值”对可以索引到资源对象。在保存资源时,ResourceDictionary视资源对象为Object类型,所以在使用资源时先要对资源对象进行类型转换,XAML编译器能够根据标签的Attribute自动识别资源类型,如果类型不对就会抛出异常,但在C#代码里检索到资源对象后,类型转换的事情就只能由我们自己来做了。

在之前的WPF介绍中说过,XAML中的标签元素对应到后台C#代码,可以看作是对象的实例化。
也就是XAML中的<xxx/> 相当于C#中的 xxx x = new xxx();
这样理解有什么好处呢?
至少我第一次接触XAML,看到Resource中加各种各样的东西是很不习惯的。
而将Resources中添加元素看成上述形式后,
其实就是 ResourceDictionary rd = new ResourceDictionary();
rd.Add(a); rd.Add(b);…
这不是和后台代码对应起来了么?
而且查看ResourceDictionary类型发现它就是一个通用集合类型。

在这里插入图片描述

ResourceDictionary可以存储任意类型的对象。在XAML代码中向Resources添加资源时需要把正确的名称空间引入到XAML代码中。看个例子:

<Window x:Class="WpfResourceDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfResourceDemo"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        Title="Resource" Height="250" Width="400">
    <Window.Resources>
        <ResourceDictionary>
            <sys:String x:Key="str">
                床前明月光,疑是地上霜。
            </sys:String>
            <sys:Double x:Key="db1">3.1415926</sys:Double>
        </ResourceDictionary>
    </Window.Resources>
    <StackPanel>
        <TextBlock Text="{StaticResource ResourceKey=str}" Margin="5"/>
        <!--TextBlock Text="{StaticResource ResourceKey=db1}" Margin="5"/-->
    </StackPanel>
</Window>

首先将System命名空间引入XAML代码并映射为sys命名空间,然后在Window.Resources属性里添加了两个资源条目,一个是string类型实例、一个是double类型实例,最后用两个TextBlock来消费这些资源(被注释掉的代码会因为数据类型不匹配而抛出异常)。程序运行效果如下:
在这里插入图片描述
因为在XAML代码中可以对集合类型内容及标签扩展进行简写,所以上面代码更常见的书写格式是这样的:

<TextBlock Text="{StaticResource str}" Margin="5"/>

在检索资源时,先查找控件自己的Resources属性,如果没有这个资源程序会沿着逻辑树向上一级控件查找,如果连最顶层容器都没有这个资源,程序就会去查找Application.Resources(也就是程序的顶级资源),如果还没找到,那就只好抛出异常了。这就好比每个界面元素都有自己的一个背包,里面可能装着各种各样的资源,使用的时候打开找一找,如果没有找到还可以去翻看上一层控件的背包,甚至找到资源或报告没有这个资源为止。

表面上看就是逐级向上寻找;这种设计非常有用,在一个窗体中(或者一块布局中)想保持同一风格,那就在顶层元素的Resource中添加统一的样式,而内部的子元素Resource不予内容,这样该窗体(布局)中的内容就都是Resource中的样式了。

如果想在C#代码中使用定义在XAML代码里的资源,大概格式是这样:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
	string text = (string)this.FindResource("str");
	this.textBlock1.Text = text;
}

或者你明确知道资源放在哪的资源词典里,就可以这样检索资源:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
	string text = (string)this.Resources["str"];
	this.textBlock1.Text = text;
}

你可能会想:如果把资源像CSS或JavaScript那样放在独立的文件中,使用时成套使用、重用时便于分发岂不更好?WPF的资源当然能够做到这一点,ResourceDictionary具有一个名为Source的属性,只要把包含资源定义的文件路径赋值给这个属性就一切搞定!举个例子,http://wpf.codeplex(现在已经关了)中包含了很多官方/半官方的WPF资源,其中包括WPF工具包和一组非常漂亮的程序皮肤,这些皮肤以资源的形式放在XAML文件中,使用时仅需把相应的XAML文件添加进项目并使用Source属性进行引用,你的程序立刻就变得光鲜照人。

<Window.Resources>
	<ResourceDictionary Source="ShinyRed.xaml"/>
</Window.Resources>

这节叫对象级资源,我觉得看完后就要产生这样一种“每个元素是一个实例(一个对象),它携带一个小背包,里面装的就是对象级资源”的思想。

2. 且“静”且“动”用资源

当资源被存储进资源词典后,我们可以用两种方式来使用这些资源——静态方式和动态方式。Static和Dynamic,当这对词一同出现的时候Static指的是程序的非执行状态而Dynamic指的是程序运行状态。对于资源的使用,Static和Dynamic也是这个意思。静态资源使用(StaticResource)指的是在程序载入内存时对资源的一次性使用,之后就不再去访问这个资源了;动态资源使用(DynamicResource)指的是在程序运行过程中仍然会去访问资源。显然,如果你确定某些资源只在程序初始化的时候使用一次、之后不会再改变,就应该使用StaticResource,而程序运行过程中还有可能改变的资源应该以DynamicResource形式使用。拿程序的主题来举例,如果程序皮肤的颜色在运行中始终不变,以StaticResource形式来使用资源就可以了;如果程序运行过程中允许用户更改程序皮肤的配色方案则必须以DynamicResource方式来使用资源。

下面例子,在Window的资源词典里放置了两个TextBlock类型资源并分别以StaticResource和DynamicResource方式来使用之:

<Window x:Class="WpfResourceDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfResourceDemo"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        Title="Resource" Height="250" Width="400">
    <Window.Resources>
        <ResourceDictionary>
            <TextBlock x:Key="res1" Text="举头望明月"/>
            <TextBlock x:Key="res2" Text="举头望明月"/>
        </ResourceDictionary>
    </Window.Resources>
    <StackPanel>
        <Button Margin="5" Content="{StaticResource res1}"/>
        <Button Margin="5" Content="{DynamicResource res2}"/>
        <Button Margin="5" Content="Update" Click="Button_Click"/>
    </StackPanel>
</Window>

界面上的第三个按钮负责在程序过程当中对资源词典里的两个资源进行改变:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.Resources["res1"] = new TextBlock() { Text = "低头思故乡" };
            this.Resources["res2"] = new TextBlock() { Text = "低头思故乡" };
        }

实际上,因为第一个按钮是以静态方式使用资源,所以尽管资源已经被更新它也不会知道。运行程序、单击第三个按钮,效果如下:
在这里插入图片描述

3. 向程序添加二进制资源

对于资源这个概念,很多WPF的初学者会感到迷惑,因为早在WPF出现之前Windows应用程序就已经能够携带资源了。Windows应用程序资源的道理与WinZip或WinRAR压缩包的道理差不多,实际上是把一些程序必须使用的资源与应用程序自身打包在一起,这样资源就不会意外丢失了(副作用就是应用程序体积会变大)。常见的应用程序资源有图标、图片、文本、音频、视频等,各种编程语言的编译器或资源编译器都有能力把这些文件编译进目标文件(最终的.exe或.dll文件),资源文件在目标文件里以二进制数据的形式存在、形成目标文件的资源段(Resource Section),使用时数据会被提取出来。

为了不把资源词典里的资源和应用程序内嵌的资源搞混,我们明确地称呼资源词典里的资源为“WPF资源”或“对象资源”,称呼应用程序的内嵌资源为“程序集资源”或“二进制资源”。特别提醒一点,WPF程序中写在<Application.Resources>…</ApplicationResources>标签内的资源仍然是WPF资源而非二进制资源。

下面看看如何向WPF程序添加二进制资源并使用它们。

如果要添加的资源是字符串而非文件,我们可以使用应用程序Properties命名空间中的Resource.resx资源文件。打开资源文件的方法是在项目管理器中展开Properties节点并双击Resources.resx。
在这里插入图片描述
在这里插入图片描述
Resources.resx文件内容的组织形式也是“键-值”对,编译后,Resources.resx会形成Properties命名空间中的Resource类,使用这个类的方法或属性就能获取资源。为了让XAML编译器能够访问这个类,一定要把Resources.resx的访问级别由Internal改为Public。利用资源文件编辑器,可以在资源文件的字符串组里添加两个条目,然后分别在XAML和C#代码中访问它们。
在这里插入图片描述

在XAML代码中使用Resources.resx中的资源,先要把程序的Properties命名空间映射为XAML命名空间,然后使用x:Static标签扩展来访问资源:

<Window x:Class="WpfResourceDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfResourceDemo"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:prop="clr-namespace:WpfResourceDemo.Properties"	映射Properties空间
        mc:Ignorable="d"
        Title="Resource" Height="250" Width="400">
    <Grid Margin="5">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="4"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="23"/>
            <RowDefinition Height="4"/>
            <RowDefinition Height="23"/>
        </Grid.RowDefinitions>
        <TextBlock Text="{x:Static prop:Resources.UserName}"/>
        <TextBlock x:Name="textBlockPassword" Grid.Row="2"/>
        <TextBox BorderBrush="Black" Grid.Column="2"/>
        <TextBox BorderBrush="Black" Grid.Column="2" Grid.Row="2"/>

    </Grid>
</Window>

C#代码中访问Resources.resx中的资源与使用:

		public MainWindow()
        {
            InitializeComponent();
            this.textBlockPassword.Text = Properties.Resources.Password;
        }

运行效果:
在这里插入图片描述
使用Resources.resx最大的好处就是便于程序的国际化、本地化。如果我想把界面改为英文版,只需要把资源的值改为相应的英文即可,如下图所示。
在这里插入图片描述
如果需要添加的资源不是字符串而是图标、图片、音频或视频,方法就不是使用Resources.resx了,WPF不支持这样做。在WPF中使用外部文件作为资源,仅需简单地将其加入项目即可。方法是在项目管理器中右击项目名称,在弹出菜单里选择Add→New Folder,按需要新建几层文件夹来组织资源,然后在恰当的文件夹右击,在弹出菜单里选择Add→Existing Item…,在文件对话框里选择文件后单击Add按钮,文件就以资源形式加入到项目中了。

如果在程序中添加了一个音频文件和一张图片,如下图,结果文件的体积就会膨胀好几兆(音频文件是7.5MB、图片是928KB):
在这里插入图片描述

在这里插入图片描述

注意:
有一点要注意的是,如果想让外部文件编译进目标成为二进制资源,必须在属性窗口中把Build Action(生成操作)属性值设为Resource(资源)。并不是每种文件都会自动设为Resource的,比如图片会,mp3就不会(虽然上例中,我都是手动设为资源的),一般情况下如果Build Action属性被设为Resource,则Copy to Output Directory(复制到输出目录)属性就设为Do not copy(不复制);如果不希望以资源的形式使用外部文件,可以把Build Action设为None,而把Copy to Output Directory设为Copy always。另外,Build Action属性的下拉列表里有一个颇具迷惑性的值Embedded Resource,不要选这个值。

在这里插入图片描述

4. 使用Pack URI路径访问二进制资源

好了!二进制资源已经被添加进我们的程序,怎样才能访问到它们呢?

WPF对二进制资源的访问有自己的一套方法,称为Pack URI路径。WPF的Pack URI路径,你只需要记住这样的格式即可:

pack://application,,,[/程序集名称;][可选版本号;][文件夹名称/]文件名称

而实际上因为pack://application,可以省略、程序集名称和版本号常使用缺省值,所以剩下的只有这个了:

[文件夹名称/]文件名称

前面的例子中我们向资源中添加了一张名为5.png的图片,它在项目中的路径是Resources/5.png,原封不动的使用这个路径就可以访问此图片了。我们用这个图片填充一个<Image/>元素并把<Image/>元素作为窗体的背景:

<Window x:Class="WpfResourceDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfResourceDemo"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        Title="Resource" Height="250" Width="400">
    <Grid>
        <Image x:Name="ImageBg" Source="Resources/5.png" Stretch="Fill"/>
        <!--Image x:Name="ImageBg" Source="pack://application:,,,/Resources/5.png" Stretch="Fill"/-->
    </Grid>
</Window>

与之等价的C#代码如下:

        public MainWindow()
        {
            InitializeComponent();
            Uri imgUri = new Uri(@"Resources/5.png", UriKind.Relative);
            // Uri imgUri = new Uri(@"pack://application;,,,/Resources/5.png", UriKind.Absolute);
            this.ImageBg.Source = new BitmapImage(imgUri);
        }

运行效果如下:
在这里插入图片描述

注意:
在使用Pack URI路径时有几点需要注意:

  • Pack URI使用从右向左的正斜线(/)表示路径。
  • 使用缩略写法意味着是相对路径,C#代码中的UriKind必须为Relative而且代表根目录的/可以省略。
  • 使用完整写法时是绝对路径,C#代码中的UriKind必须为Absolute并且代表根目录的/不能省略。
  • 使用相对路径时可以借助类似DOS的语法进行导航,比如./代表同级目录、…/代表父级目录。

三、总结

资源章节篇幅不长,

  • 理解对象级资源是啥
  • 程序中要修改时用Dynamic动态使用,否则用Static
  • 了解WPF中二进制资源的添加
  • 如何用Uri来访问二进制资源
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深入浅出WPF(Windows Presentation Foundation)是一本介绍WPF的书籍,适合初学者。本书共分为以下几章: 第一章:WPF简介 这一章主要介绍WPF的背景和优势,以及与传统的Windows Forms应用程序开发的对比。 第二章:XAML基础 XAML(可扩展应用程序标记语言)是WPF的核心,本章通过一些简单的示例介绍XAML的基本概念和语法。 第三章:WPF控件 WPF提供了丰富的控件,本章依次介绍了常用的Button、TextBox、ComboBox和ListBox等控件的用法和属性。 第四章:布局和容器 WPF的布局和容器可以帮助我们更好地管理和组织控件,本章详细介绍了Grid、StackPanel和WrapPanel等布局和容器的使用。 第五章:数据绑定 数据绑定是WPF的重要特性之一,可以实现数据和UI之间的自动同步,本章通过示例演示了常见的数据绑定方式。 第六章:样式和模板 WPF的样式和模板可以帮助我们更好地定制和美化应用程序的外观,本章介绍了如何定义和应用样式和模板。 第七章:命令和事件 WPF的命令和事件机制是实现交互的重要手段,本章介绍了如何定义和使用命令,以及如何处理事件。 第八章:动画和效果 WPF提供了强大的动画和效果功能,可以使应用程序更加生动和吸引人,本章介绍了常用的动画和效果的实现方式。 第九章:MVVM架构 MVVM(Model-View-ViewModel)是一种经典的软件架构模式,在WPF开发中被广泛应用,本章介绍了MVVM的基本原理和实现方式。 第十章:高级主题 本章涵盖了一些高级的WPF主题,如自定义控件、多文档界面和异步编程等。 通过学习本书,读者可以全面了解WPF的基础知识和常用技术,能够使用WPF开发出功能强大、界面美观的应用程序。无论是从零开始学习WPF,还是希望系统地复习和巩固WPF知识的读者,本书都是一本不可或缺的参考资料。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值