WPF 应用程序资源、内容和数据文件

Microsoft Windows 应用程序通常依赖包含不可执行数据的文件,如Extensible Application Markup Language (XAML)、图像、视频和音频。 Windows Presentation Foundation (WPF) 为配置、识别和使用这些类型的数据文件(称为应用程序数据文件)提供了特殊支持。 这种支持主要针对一组特定的应用程序数据文件类型,包括:

  • 资源文件:编译到可执行或库 WPF 程序集中的数据文件。

  • 内容文件:与可执行 WPF 程序集具有显式关联的独立数据文件。

  • 源站点文件:与可执行 WPF 程序集没有关联的独立数据文件。

这三种类型的文件之间的一个重要区别是:资源文件和内容文件在生成时是已知的;程序集明确地知道它们的存在。 但是对于源站点文件,程序集可能完全不知道它们,或者通过 pack uniform resource identifier (URI) 引用知道它们的存在;在后一种情况下,不能保证被引用的源站点文件实际存在。

为了引用应用程序数据文件,Windows Presentation Foundation (WPF) 使用 Pack uniform resource identifier (URI) 方案,这将在 WPF 中的 Pack URI中详细介绍。

本主题介绍如何配置和使用应用程序数据文件。

本主题包括下列各节。

  • 资源文件
  • 内容文件
  • 源站点文件。
  • 更改生成类型后重新生成
  • 相关主题

资源文件

如果应用程序数据文件必须始终可供某个应用程序使用,那么保证可用性的唯一方法是将其编译到应用程序的主可执行程序集中,或者它所引用的程序集中。 这种类型的应用程序数据文件称为“资源文件”。

应在以下情况下使用资源文件:

  • 在将资源文件编译到程序集中之后,不需要更新资源文件的内容。

  • 希望通过减少文件依赖项的数量来简化应用程序分发的复杂性。

  • 应用程序数据文件必须是可本地化的(请参见 WPF 全球化和本地化概述)。

注意注意

资源词典(以 ResourceDictionary 作为顶级元素的 XAML 文件)不是 WPF 资源文件;而 WPF 资源文件可以是资源词典,但资源词典不必是资源文件(请参见 ResourceDictionary)。

此外,WPF 资源文件不同于嵌入或链接类型的资源,这些资源可以利用对程序集资源的核心 .NET Framework 支持进行配置(请参见管理应用程序资源)。虽然 WPF 资源文件确实利用了核心 .NET Framework 嵌入资源支持,但是使用 pack URIs 访问 WPF 资源文件的功能比使用命名空间更加容易。

配置资源文件

在 WPF 中,资源文件是作为 Resource 项包含在 Microsoft build engine (MSBuild) 项目中的文件。

<Project "xmlns=https://schemas.microsoft.com/developer/msbuild/2003" ... >
  ...
  <ItemGroup>
    <Resource Include="ResourceFile.xaml" />
  </ItemGroup>
  ...
</Project>
注意注意

在 Microsoft Visual Studio 中,可通过将一个文件添加到项目并将其 Build Action 设置为 Resource 来创建资源文件。

项目生成时,MSBuild 会将该资源编译到程序集中。

使用资源文件

若要加载资源文件,可以调用 Application 类的 GetResourceStream 方法,同时传递标识所需资源文件的 pack URI。 GetResourceStream 将返回一个 StreamResourceInfo 对象,该对象将该资源文件作为 Stream 公开,并描述其内容类型。

例如,下面的代码演示如何使用 GetResourceStream 来加载 Page 资源文件,并将其设置为 Frame (pageFrame) 的内容:

' Navigate to xaml page
Dim uri As New Uri("/PageResourceFile.xaml", UriKind.Relative)
Dim info As StreamResourceInfo = Application.GetResourceStream(uri)
Dim reader As New System.Windows.Markup.XamlReader()
Dim page As Page = CType(reader.LoadAsync(info.Stream), Page)
Me.pageFrame.Content = page
// Navigate to xaml page
Uri uri = new Uri("/PageResourceFile.xaml", UriKind.Relative);
StreamResourceInfo info = Application.GetResourceStream(uri);
System.Windows.Markup.XamlReader reader = new System.Windows.Markup.XamlReader();
Page page = (Page)reader.LoadAsync(info.Stream);
this.pageFrame.Content = page;

虽然通过调用 GetResourceStream 可以访问 Stream,但是需要进行一些额外的工作来将其转换为将要设置的属性的类型。 可以改为通过使用代码将资源文件直接加载到某个类型的属性,来让 WPF 负责打开和转换 Stream

下面的示例演示如何使用代码将 Page 直接加载到 Frame (pageFrame)中。

Dim pageUri As New Uri("/PageResourceFile.xaml", UriKind.Relative)
Me.pageFrame.Source = pageUri
Uri pageUri = new Uri("/PageResourceFile.xaml", UriKind.Relative);
this.pageFrame.Source = pageUri;

下面的示例是与上例等效的标记。

<Frame Name="pageFrame" Source="PageResourceFile.xaml" />

作为资源文件的应用程序代码文件

可以使用 pack URIs 来引用一组特殊的 WPF 应用程序代码文件,包括窗口、页、流文档和资源词典。 例如,可以将 Application.StartupUri 属性设置为一个 pack URI,该 URL 引用要在应用程序启动时加载的窗口或页。

<Application
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    StartupUri="SOOPage.xaml" />

当 XAML 文件作为一个 Page 项包含在 Microsoft build engine (MSBuild) 项目中时,可以进行此操作。

<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003" ... >
  ...
  <ItemGroup>
    <Page Include="MainWindow.xaml" />
  </ItemGroup>
  ...
</Project>
注意注意

在 Visual Studio 中,可以将新的 WindowNavigationWindowPageFlowDocumentResourceDictionary 添加到项目,标记文件的 Build Action 将默认为 Page。

在编译含有 Page 项的项目时,XAML 项将转换为二进制格式,并编译到关联的程序集中。 因此,可以像使用典型的资源文件一样使用这些文件。

注意注意

如果 XAML 文件配置为 Resource 项,并且没有代码隐藏文件,则原始 XAML 将编译到程序集中,而不是原始 XAML 的二进制版本。

内容文件

内容文件是作为松散文件与可执行程序集一起分发的。 虽然它们不编译到程序集中,但编译程序集时所使用的元数据建立了与每个内容文件的关联。

如果应用程序需要一组特定的应用程序数据文件,并且您希望能够更新这些文件,而无需重新编译使用它们的程序集,则应该使用内容文件。

配置内容文件

若要将内容文件添加到项目中,必须在 Content 项中包含一个应用程序数据文件。 此外,因为内容文件不直接编译到程序集中,所以需要设置 MSBuild CopyToOutputDirectory 元数据元素,以指定将内容文件复制到一个相对于生成的程序集的位置。 如果希望在每次生成项目时都将资源复制到生成输出文件夹,可将 CopyToOutputDirectory 元数据元素设置为 Always 值。 否则,可以使用 PreserveNewest 值来确保只有最新版本的资源才会复制到生成输出文件夹。

下面演示的是一个配置为内容文件的文件,只有在将新版本的资源添加到项目时,它才会复制到生成输出文件夹。

<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003" ... >
  ...
  <ItemGroup>
    <Content Include="ContentFile.xaml">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
  ...
</Project>
注意注意

在 Visual Studio 中,可通过将一个文件添加到项目并将其 Build Action 设置为 Content 来创建内容文件,然后将其 Copy to Output Directory 设置为 Copy always(与 Always 相同)和 Copy if newer(与 PreserveNewest 相同)。

生成项目时,对于每一个内容文件,将有一个相应的 AssemblyAssociatedContentFileAttribute 特性编译到程序集的元数据中。

[assembly: AssemblyAssociatedContentFile("ContentFile.xaml")]

AssemblyAssociatedContentFileAttribute 的值表示内容文件相对于其在项目中的位置的路径。 例如,如果内容文件位于某个项目子文件夹中,则附加路径信息将合并到 AssemblyAssociatedContentFileAttribute 值中。

[assembly: AssemblyAssociatedContentFile("Resources/ContentFile.xaml")]

AssemblyAssociatedContentFileAttribute 值也是内容文件在生成输出文件夹中的路径的值。

使用内容文件

若要加载内容文件,可以调用 Application 类的 GetContentStream 方法,同时传递标识所需内容文件的 pack URI。 GetContentStream 将返回一个 StreamResourceInfo 对象,该对象将该内容文件作为 Stream 公开,并描述其内容类型。

例如,下面的代码演示如何使用 GetContentStream 来加载 Page 内容文件,并将其设置为 Frame (pageFrame) 的内容:

' Navigate to xaml page
Dim uri As New Uri("/PageContentFile.xaml", UriKind.Relative)
Dim info As StreamResourceInfo = Application.GetContentStream(uri)
Dim reader As New System.Windows.Markup.XamlReader()
Dim page As Page = CType(reader.LoadAsync(info.Stream), Page)
Me.pageFrame.Content = page
// Navigate to xaml page
Uri uri = new Uri("/PageContentFile.xaml", UriKind.Relative);
StreamResourceInfo info = Application.GetContentStream(uri);
System.Windows.Markup.XamlReader reader = new System.Windows.Markup.XamlReader();
Page page = (Page)reader.LoadAsync(info.Stream);
this.pageFrame.Content = page;

虽然通过调用 GetContentStream 可以访问 Stream,但是需要进行一些额外的工作来将其转换为将要设置的属性的类型。 可以改为通过使用代码将资源文件直接加载到某个类型的属性,来让 WPF 负责打开和转换 Stream

下面的示例演示如何使用代码将 Page 直接加载到 Frame (pageFrame)中。

Dim pageUri As New Uri("/PageContentFile.xaml", UriKind.Relative)
Me.pageFrame.Source = pageUri
Uri pageUri = new Uri("/PageContentFile.xaml", UriKind.Relative);
this.pageFrame.Source = pageUri;

下面的示例是与上例等效的标记。

<Frame Name="pageFrame" Source="PageContentFile.xaml" />

源站点文件。

资源文件与同其一起分发的程序集有显式关系,这一关系由 AssemblyAssociatedContentFileAttribute 定义。 但是,有些情况下可能需要在程序集和应用程序数据文件之间建立隐式关系或不存在的关系,这些情况包括:

  • 编译时文件不存在。

  • 在运行之前您不知道程序集将需要哪些文件。

  • 您希望能够更新文件,而又无需重新编译与这些文件关联的程序集。

  • 应用程序使用大型数据文件,如音频和视频,并且您希望仅在用户选择下载时才下载这些文件。

使用传统的 URI 方案可以加载这些类型的文件,如 file:/// 和 http:// 方案。

<Image Source="file:///C:/DataFile.bmp" />
<Image Source="http://www.datafilewebsite.com/DataFile.bmp" />

但是,file:/// 和 http:// 方案要求应用程序具有完全信任。 如果应用程序是从 Internet 或 Intranet 启动的 XAML browser application (XBAP),并且它请求的权限集只针对从这些位置启动的应用程序,则松散文件只能从应用程序的源站点(启动位置)加载。 这样的文件称为“源站点”文件。

虽然源站点文件并不仅限于部分可信应用程序,但它却是部分可信应用程序的唯一选择。 完全可信应用程序可能仍然需要加载它们在生成时所不知道的应用程序数据文件;但是完全可信应用程序可以使用 file:///,应用程序数据文件很可能将安装在该应用程序集的同一个文件夹或其子文件夹中。 在此情况下,使用源站点引用比使用 file:/// 更加容易,因为使用 file:/// 需要找出文件的完整路径。

注意注意

源站点文件不会与 XAML browser application (XBAP) 一起缓存在客户端计算机上,而内容文件却会如此。所以,只有在专门请求下载源站点文件时,才会下载它们。如果 XAML browser application (XBAP) 应用程序有大型媒体文件,则将这些文件配置为源站点文件意味着应用程序初始启动更快,并且这些文件只会按需下载。

配置源站点文件

如果源站点文件在编译时不存在或未知,则需要使用传统部署机制来确保所需文件在运行时可用,包括使用 XCopy 命令行程序或 Microsoft Windows Installer。

如果在编译时您知道要放置于源站点的文件,但仍然希望避免显式依赖项,则可以将这些文件作为 None 项添加到 Microsoft build engine (MSBuild) 项目中。 与内容文件一样,需要设置 MSBuild CopyToOutputDirectory 特性来指定将源站点文件复制到一个相对于生成的程序集的位置(通过指定 Always 值或 PreserveNewest 值)。

<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003" ... >
  ...
  <None Include="PageSiteOfOriginFile.xaml">
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </None>
  ...
</Project>
注意注意

在 Visual Studio 中,可通过将一个文件添加到项目,并将其 Build Action 设置为 None 来创建源站点文件。

在项目生成时,MSBuild 将指定文件复制到生成输出文件夹。

使用源站点文件

若要原始文件的站点,可以调用 Application 类的 GetRemoteStream 方法,同时传递标识原始文件的所需站点的 pack URI。 GetRemoteStream 将返回一个 StreamResourceInfo 对象,该对象将原始文件的该站点作为 Stream 公开,并描述其内容类型。

例如,下面的代码演示如何使用 GetRemoteStream 来加载 Page 源站点文件,并将其设置为 Frame (pageFrame) 的内容。

' Navigate to xaml page
Dim uri As New Uri("/SiteOfOriginFile.xaml", UriKind.Relative)
Dim info As StreamResourceInfo = Application.GetRemoteStream(uri)
Dim reader As New System.Windows.Markup.XamlReader()
Dim page As Page = CType(reader.LoadAsync(info.Stream), Page)
Me.pageFrame.Content = page
// Navigate to xaml page
Uri uri = new Uri("/SiteOfOriginFile.xaml", UriKind.Relative);
StreamResourceInfo info = Application.GetRemoteStream(uri);
System.Windows.Markup.XamlReader reader = new System.Windows.Markup.XamlReader();
Page page = (Page)reader.LoadAsync(info.Stream);
this.pageFrame.Content = page;

虽然通过调用 GetRemoteStream 可以访问 Stream,但是需要进行一些额外的工作来将其转换为将要设置的属性的类型。 可以改为通过使用代码将资源文件直接加载到某个类型的属性,来让 WPF 负责打开和转换 Stream

下面的示例演示如何使用代码将 Page 直接加载到 Frame (pageFrame)中。

Dim pageUri As New Uri("pack://siteoforigin:,,,/Subfolder/SiteOfOriginFile.xaml", UriKind.Absolute)
Me.pageFrame.Source = pageUri
Uri pageUri = new Uri("pack://siteoforigin:,,,/SiteOfOriginFile.xaml", UriKind.Absolute);
this.pageFrame.Source = pageUri;

下面的示例是与上例等效的标记。

<Frame Name="pageFrame" Source="pack://siteoforigin:,,,/SiteOfOriginFile.xaml" />

更改生成类型后重新生成

在更改应用程序数据文件的生成类型后,需要重新生成整个应用程序以确保应用这些更改。 如果只生成应用程序,则不会应用更改。

请参见

概念

WPF 中的 Pack URI