WPF 學習筆記 - 7. Resource

1. 二進制資源

WPF 支持三種方式的二進制資源,這些資源可以非常方便地在 XAML 中使用。

  • Resource: 將資源嵌入程序集中,和 Embedded Resource 有點像。區別在於 WPF 將相關資源打包到 .Resources 文件,然後再由編譯器嵌入到程序集文件中。WPF 默認的 URI 訪問方式是不支持 Embedded Resource 的。
  • Content: 資源不會嵌入到程序集,僅僅在程序集清單中添加一條記錄,這和以往我們所熟悉的方式一樣。資源文件必須隨其他程序集文件一起部署到目標目錄。
  • Loose File: 這類資源通常是運行期動態確定或加入的。
uploads/200811/01_131926_5.png


WPF 通過統一資源標識符(URI)訪問相關資源文件。

(1) 訪問 Resource / Content 資源
<Image Source="/a.png" />
<Image Source="/xxx/x.png" />

(2) 訪問鬆散資源文件(Loose File)
<Image Source="pack://siteOfOrigin:,,,/a.png" />
<Image Source="c:/test/a.png" />

XAML 編譯時需要驗證所有資源有效性,因此在使用鬆散資源文件時必須使用有效的 URI 路徑。 "pack://" 表示 Package URI,"pack://siteOfOrigin:,,," 表示從部署位置開始,相應的還有 "pack://application:,,," 上面的 Resource 資源全路徑應該是 "pack://application:,,,/a.png",只不過通常情況下我們使用省略寫法而已。
<Image Source="pack://application:,,,/a.png" />

其他的 URI 寫法還包括:
<Image Source="http://www.qidian.com/images/logo.gif" />
<Image Source="//server1/share/logo.gif" />
<Image Source="file://c:/test/logo.gif" />

(3) 訪問其他程序集中的資源文件

提供一個可替換的專用資源 DLL 也是一種常用編程手法,尤其是多語言或者換膚機制。WPF 訪問其他程序集資源文件的語法有點古怪。
pack://application:,,,/AssemblyReference;Component/ResourceName
  • 其他程序集的資源必須以 Resource 方式嵌入。
  • 不能省略 "/AssemblyReference" 前面的反斜槓。
  • Component 是關鍵字,必須包含在 URI 中。
<Image Source="pack://application:,,,/Learn.Library;component/s.gif" />
<Image Source="/Learn.Library;component/s.gif" />

2. 邏輯資源

邏輯資源是一些存儲在元素 Resources 屬性中的對象,這些 "預定義" 的對象被一個或多個子元素所引用或共享。
<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Window.Resources>
    <ContentControl x:Key="label1">Hello, World!</ContentControl>
    <ImageSource x:Key="image1">/a.png</ImageSource>
  </Window.Resources>
  <Grid>
    <Label Content="{StaticResource label1}" />
    <Image Source="{StaticResource image1}" />
  </Grid>
</Window>

當然,我們也可以寫成下面這種格式。
<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Window.Resources>
    <ContentControl x:Key="label1">Hello, World!</ContentControl>
    <ImageSource x:Key="image1">/a.png</ImageSource>
  </Window.Resources>
  <Grid>
    <Label>
      <Label.Content>
        <StaticResource ResourceKey="label1" />
      </Label.Content>
    </Label>
    <Image>
      <Image.Source>
        <StaticResource ResourceKey="image1" />
      </Image.Source>
    </Image>
  </Grid>
</Window>

我們甚至可以用邏輯資源定義一個完整的子元素。
<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Window.Resources>
    <Label x:Key="label1" Content=" Hello, World!" />
    <Image x:Key="image1" Source="/a.png" />
  </Window.Resources>
  <Grid>
    <StaticResource ResourceKey="label1" />
    <StaticResource ResourceKey="image1" />
  </Grid>
</Window>

邏輯資源有個限制,那就是這些繼承自 Visual 的元素對象只能有一個父元素,也就是說不能在多個位置使用,因爲多次引用的是同一個對象實例。
<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Window.Resources>
    <Image x:Key="image1" Source="/a.png" />
  </Window.Resources>
  <Grid>
    <StaticResource ResourceKey="image1" />
    <StaticResource ResourceKey="image1" />
  </Grid>
</Window>

你將看到下面這樣一個錯誤提示。

uploads/200811/01_131520_1.png


解決方法就是添加 "x:Shared=False",這樣每次引用都會生成一個新的邏輯資源對象。
<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Window.Resources>
    <Image x:Key="image1" x:Shared="False" Source="/a.png" />
  </Window.Resources>
  <Grid>
    <StaticResource ResourceKey="image1" />
    <StaticResource ResourceKey="image1" />
  </Grid>
</Window>

資源查找: 引用邏輯資源時首先會查找父元素 Resources 集合,如未找到,會逐級檢查更上層的父元素。如果直到根元素依然未找到有效的邏輯資源定義,那麼 WPF 會檢查 Application.Resources (App.xaml 中定義) 和系統屬性集合(SystemParamters 等)。每個獨立的資源字典中鍵名不能重複,但在多個不同層級的資源字典中允許重複,離資源引用最近的那個邏輯資源項被優先採納。
<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Window.Resources>
    <Label x:Key="label1" Content=" Hello, World!" />
  </Window.Resources>
  <Grid>
    <Grid.Resources>
      <Label x:Key="label1" Content=" Hello, C#!" />
    </Grid.Resources>
    <StackPanel>
      <StaticResource ResourceKey="label1" />
    </StackPanel>
  </Grid>
</Window>

將顯示 "Hello, C#!"。

邏輯資源的引用方式又分類 "靜態(StaticResource)" 和 "動態(DynamicResource)" 兩種方式,區別在於靜態引用僅在第一次資源加載時被應用,而動態引用則會在資源被更改時重新應用。
<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Window.Resources>
    <ContentControl x:Key="label1" x:Shared="False">Hello, World!</ContentControl>
  </Window.Resources>
  <x:Code>
    private void button1_Click(object sender, RoutedEventArgs e)
    {
      this.Resources["label1"] = "Hello, C#!";
    }
  </x:Code>
  <Grid>
    <StackPanel>
      <Label x:Name="label1" Content="{StaticResource label1}" />
      <Label x:Name="label2" Content="{DynamicResource label1}" />
      <Button x:Name="button1" Click="button1_Click">Test</Button>
    </StackPanel>
  </Grid>
</Window>

單擊按鈕後,你會發現 label2 實時反應了資源的修改。

動態資源只能用於設置依賴屬性值,因此它不能像靜態資源那樣引用一個完整的元素對象。
<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Window.Resources>
    <Label x:Key="label" x:Shared="False" Content="Hello, World!" />
  </Window.Resources>
  <Grid>
    <StackPanel>
      <DynamicResource ResourceKey="label" />
    </StackPanel>
  </Grid>
</Window>

這將導致出現下圖這樣的異常,而靜態資源則沒有這個問題。

uploads/200811/01_131525_2.png


當然,靜態資源也有另外一個麻煩,就是不支持前向引用(Forward Reference),也就是說我們必須先定義資源,然後才能使用靜態引用。
<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Grid>
    <StackPanel>
      <Label Content="{StaticResource label}" />
    </StackPanel>
  </Grid>
  <Window.Resources>
    <ContentControl x:Key="label">Hello, World!</ContentControl>
  </Window.Resources>
</Window>

屬於靜態資源的異常出現了,當然動態資源是沒有這個問題的。

uploads/200811/01_131530_3.png


如果有必要的話,我們可以將邏輯資源分散定義在多個 XAML 文件(Resource Dictionary) 中。

uploads/200811/01_131535_4.png


Dictionary1.xaml
<ResourceDictionary 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <ContentControl x:Key="label1">Hello, World!</ContentControl>
</ResourceDictionary>

Dictionary2.xaml
<ResourceDictionary 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <ContentControl x:Key="label2">Hello, C#!</ContentControl>
</ResourceDictionary>

Window1.xaml
<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Window.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Dictionary1.xaml" />
        <ResourceDictionary Source="Dictionary2.xaml" />
      </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
  </Window.Resources>
  <Grid>
    <StackPanel>
      <Label Content="{DynamicResource label1}" />
      <Label Content="{DynamicResource label2}" />
    </StackPanel>
  </Grid>
</Window>

如果合併的字典中有重複的鍵值,那麼最後加入的資源將優先。比如 Dictionary1.xaml 和 Dictionary2.xaml 都有一個 x:Key="label1" 的資源,那麼 Dictionary2.label1 將獲勝。

在程序代碼中我們可以用 FindResource/TryFindResource 設置靜態資源,用 SetResourceReference 設置動態資源。
this.labelA.Content = (ContentControl)this.labelA.FindResource("key1"); // StaticResource
this.labelB.SetResourceReference(Label.ContentProperty, "key2"); // DynamicResource
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章