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

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

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

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

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

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

为了引用应用程序数据文件,Windows Presentation Foundation (WPF) 使用包统一资源标识符 (URI) 方案(WPF 中的包 URI 中对此进行了详细说明)。

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

资源文件

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

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

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

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

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

注意

本部分中所述的资源文件不同于 XAML 资源 中所述的资源文件,也不同于管理应用程序资源 (.NET) 中所述的嵌入或链接资源。

配置资源文件

在 WPF 中,资源文件是作为 Resource 项包含在 Microsoft 生成引擎 (MSBuild) 项目中的文件。

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

注意

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

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

使用资源文件

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

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

// 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;
' 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

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

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

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

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

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

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

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

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

当 XAML 文件作为 Page 项包含在 MSBuild 项目中,可以执行此操作。

<Project xmlns="http://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="http://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 方法,并传递用于标识所需内容文件的包 URI。 GetContentStream 返回 StreamResourceInfo 对象,该对象将内容文件公开为 Stream 并描述其内容类型。

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

// 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;
' 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

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

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

Uri pageUri = new Uri("/PageContentFile.xaml", UriKind.Relative);
this.pageFrame.Source = pageUri;
Dim pageUri As New Uri("/PageContentFile.xaml", UriKind.Relative)
Me.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 浏览器应用程序 (XBAP),并且它请求的权限集只针对从这些位置启动的应用程序,则松散文件只能从应用程序的源站点(启动位置)加载。 此类文件称为“源站点”文件

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

注意

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

配置源站点文件

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

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

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

注意

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

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

使用源站点文件

若要加载源站点文件,可以调用 Application 类的 GetRemoteStream 方法,并传递用于标识所需源站点文件的包 URI。 GetRemoteStream 返回 StreamResourceInfo 对象,该对象将源站点文件公开为 Stream 并描述其内容类型。

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

// 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;
' 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

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

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

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

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

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

更改生成类型后重新生成

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

另请参阅