[WPF]淺析資源引用(pack URI)

czwy發表於2023-11-07

WPF中我們引用資源時常常提到一個概念:pack URI,這是WPF標識和引用資源最常見的方式,但不是唯一的方式。本文將介紹WPF中引用資源的幾種方式,並回顧一下pack URI標識引用在不同位置的資原始檔的寫法。

WPF中引用資源的幾種方式

WPF中使用URI標識和載入位於各種位置的檔案,包括當前程式集資原始檔、其他程式集資原始檔、本地磁碟檔案、網路共享檔案、web站點檔案。

程式集資原始檔

程式集資原始檔是最常見的一種情況。這裡程式集資源指的是資原始檔屬性的生成操作(Build Action)為Resource的檔案,而非嵌入的資源(Emmbedded Resource)。程式集中的資原始檔通常使用相對URI來引用,例如:

<ImageBrush x:Key="imgbrush" ImageSource="images/111.jpg"/>   //本地程式集中資源引用的寫法
<ImageBrush x:Key="imgbrush" ImageSource="/ResourceDll;component/images/111.jpg"/>   //引用的程式集中資源引用的寫法

也可以使用絕對Pack URI語法,例如

<ImageBrush x:Key="imgbrush" ImageSource="pack://application:,,,/images/111.jpg"/>     //本地程式集中資源引用的寫法
<ImageBrush x:Key="imgbrush" ImageSource="pack://application:,,,/ResourceDll;component/images/111.jpg"/>   //引用的程式集中資源引用的寫法

本地磁碟檔案

直接引用本地磁碟檔案的方式不常見。這種方式引用本地檔案會佔用檔案,本地檔案無法修改或者刪除,因此不推薦此方式。這裡只是舉例講解。

<ImageBrush x:Key="imgbrush" ImageSource="d:\\tmp\\新建資料夾\\123.jpg"/> 

網路共享檔案

網路共享檔案和本地磁碟檔案類似,會佔用檔案。可以使用UNC或者URI的方式引用。

<ImageBrush x:Key="imgbrush" ImageSource="\\192.168.0.1\tmp\新建資料夾\123.jpg"/>    UNC方式引用
<ImageBrush x:Key="imgbrush" ImageSource="file://192.168.0.1\tmp\新建資料夾\123.jpg"/>    URI方式引用

web站點檔案

少數場景下會在WPF中使用web站點資源,比如使用者頭像。web站點資源主要以http/https協議的url載入,url作為URI的子集,因此可以直接引用。實際開發中不建議直接引用url,因為請求網路資源需要時間,這可能導致UI短暫卡頓。建議開啟執行緒把網路資源讀到記憶體中使用。

<ImageBrush x:Key="imgbrush" ImageSource="https://pic.cnblogs.com/default-avatar.png"/>

上述示例中都是在XAML中宣告式的語法引用資源,本質還是使用Uri類,因此在後臺程式碼中使用Uri類就行。

// 絕對URI (預設)
Uri absoluteUri = new Uri("pack://application:,,,/images/111.jpg", UriKind.Absolute);
// 相對URI
Uri relativeUri = new Uri("images/111.jpg", UriKind.Relative);

Pack URI方案

pack URI的語法看起來很奇怪,它是來自開放式打包約定 (OPC)規範中XPS(XML Paper Specification)標準,有使用openxml解析Word/PPT檔案經驗的朋友可能熟悉這個規範。OPC 規範利用RFC 2396(統一資源識別符號 (URI):一般語法)的擴充套件性來定義pack URI方案。

URI所指定的方案(schemes)由其字首定義;httpftptelnetfile 是比較常見的協議方案(schemes)。pack URI使用“pack”作為它的方案(schemes),並且包含兩個元件:授權和路徑。 pack URI的格式為:pack://authority/path。authority指定包含部件的包的型別,而path 指定部件在包內的位置。前邊示例程式碼中application:,,,就是授權(authority),/images/111.jpg或者/ResourceDll;component/images/111.jpg就是路徑(path)。這裡也可以理解為巢狀在方案(schemes)為pack://的uri中的uri。由於是巢狀在內部的uri,授權(authority)原本應是application:///中的斜槓轉義為逗號。路徑中必須對保留字元(如“%”和“?”)進行轉義。詳細資訊可參閱開放式打包約定 (OPC)規範

標準的URI協議方案有30種左右,由隸屬於國際網際網路資源管理的非營利社團 ICANN(Internet Corporation for Assigned Names and Numbers,網際網路名稱與數字地址分配機構)的 IANA(Internet Assigned Numbers Authority,網際網路號碼分配局)管理頒佈。詳細協議方案參見:http://www.iana.org/assignments/uri-schemes

在WPF中,用程式(包)可以包含一個或多個檔案(部件),包括:

  • 當前程式集內的資原始檔
  • 引用的程式集內的資原始檔
  • 內容檔案
  • 源站點檔案

為了訪問這些型別的檔案,WPF 支援兩種授權:application:///siteoforigin:///。 application:/// 授權標識在編譯時已知的應用程式資料檔案,包括資原始檔和內容檔案。 siteoforigin:/// 授權標識源站點檔案。 下圖顯示了每種授權的範圍。

iamge

pack URI語法示例

前邊提到pack URI由授權和路徑組成,當前程式集、引用的程式集內的資原始檔,以及內容檔案的授權都是application:///,源站點檔案的授權是siteoforigin:///(用於XAML瀏覽器應用程式)。

當前程式集資原始檔

當前程式集資原始檔的路徑是資原始檔相對程式集專案資料夾根目錄的路徑。需要注意的是這裡所說的相對於程式集專案資料夾根目錄表達的是從哪裡開始作為根目錄進行定址,當使用pack://這樣絕對URI表示時,路徑應該用根目錄符號/開始。下圖中111.jpg位於專案的根目錄,它的pack URI就是:

pack://application:,,,/111.jpg

BlindsShader.ps位於子目錄中,它的pack URI就是:

pack://application:,,,/Shader/ShaderSource/BlindsShader.ps

image

引用程式集資原始檔

當需要引用另一個程式集中的資原始檔時,路徑需要指明程式集的名稱。路徑需符合以下的格式:

pack://application:,,,AssemblyShortName{;Version}{;PublicKey};component/ResourceName
  • AssemblyShortName是引用的程式集的短名稱,是必選項
  • Version是引用的程式集的版本。此部分在載入兩個或多個具有相同短名稱的引用程式集時使用,是可選項。
  • PublicKey是引用的程式集的簽名公鑰。此部分在載入兩個或多個具有相同短名稱的引用程式集時使用,是可選項。
  • component指定所引用的程式集是從本地程式集引用的,此處是固定寫法
  • ResourceName是資原始檔的名稱,包括其相對於所引用程式集的專案資料夾根目錄的路徑。

內容檔案

前邊提到的資原始檔都是生成操作(Build Action)為Resource的檔案,是會編譯到程式集中。內容檔案是生成操作(Build Action)為內容(Content)的檔案,並不會編譯到程式集中,通常是將檔案屬性中複製到輸出目錄(CopyToOutputDirectory)選為始終複製(Always)或者如果較新則複製(PreserveNewest),將檔案儲存到程式執行目錄中。內容檔案主要可以解決以下問題:

  • 改變資原始檔時,需要重新編譯應用程式;
  • 資原始檔比較大,導致編譯的程式集也比較大;
  • WPF聲音文類不支援程式集資源,無法從資源流中析取音訊檔案並播放。

內容檔案本質上也是本地磁碟檔案,但生成專案時,會將 AssemblyAssociatedContentFileAttribute 屬性編譯到每個內容檔案的程式集的後設資料內,AssemblyAssociatedContentFileAttribute 的值表示內容檔案相對於其在專案中的位置的路徑[^2],可以採用pack URI的方式載入。內容檔案的路徑是其相對於應用程式的主可執行程式集的檔案系統位置的路徑。其格式如下:

pack://application:,,,/ContentFile.wav

源站點檔案

源站點檔案主要針對XAML瀏覽器應用程式(XBAP)設計,編譯XAML瀏覽器應用程式(XBAP)將資原始檔分離出程式集,減少檔案大小,在需要請求下週源站點檔案時,才下載它們到客戶端計算機[^2]。現在基本不適用該技術,本文不再詳細介紹,感興趣可以檢視文末參考資料。

參考

[^1] https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/app-development/pack-uris-in-wpf?view=netframeworkdesktop-4.8
[^2] https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/app-development/wpf-application-resource-content-and-data-files?view=netframeworkdesktop-4.8