WPF的樣式機制以來於資源體系來定位樣式。正如你在第5章看到的,樣式在 元素的資源片段中定義,而且樣式通過其名字被引用,正如示例6-18所示:
示例6-18
<Window x:Class="ResourcePlay.Window1" Text="ResourcePlay"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">
<Window.Resources>
<Style x:Key="myStyle">
<Setter Property="Button.FontSize" Value="36" />
</Style>
</Window.Resources>
<Grid>
<Button Style="{StaticResource myStyle}">Hello</Button>
</Grid>
</Window>
然而,如何定義一個樣式,使之自動的應用到一個元素,而無需顯示指定要 引用的資源——這是可以實現的,而且非常有用——當你需要把一個樣式應用到 具有獨特類型的所有元素上,而不是把資源引用添加到每個元素上。示例6-19對 示例6-18做了一些修改,展示了隱式聲明這一功能。
示例6-19
<Window x:Class="ResourcePlay.Window1" Text="ResourcePlay"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">
<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Button.FontSize" Value="36" />
</Style>
</Window.Resources>
<Grid>
<Button>Hello</Button>
</Grid>
</Window>
注意到Button標簽不再有其特定的Style屬性。然而,這個樣式仍然通過 TargetType應用到Button上,而不是定義一個key,這個樣式使用x:Type來設置 TargetType,於是通知XAML為這個TargetType類提供一個System.Type對象。
如果FrameworkElement沒有顯示指定Style,它總是會尋找一個使用其自身類 型的樣式資源,作為其Target類型。
如果你建立了一些非樣式的資源,例如SolidColorBrush,同時設置其x:Key 為某個UI元素的類型,如果試著使用該元素的類型就會發生一個錯誤。這是因為 你創建了一個帶有TargetType的Style卻沒有指定x:Key,x:Key隱式地設置為同 TargetType一樣。這個Key用於定位style。因此,通常而言,你應該避免將 x:Key設置為Type類型的對象。
因為元素會在資源中搜索它的樣式,你可以利用系統級別的資源。你可以定 義一個樣式資源在局部范圍內,如果你僅僅希望影響少量的元素;或者在一個廣 義范圍上,例如Window.Resource;或者在應用程序的范圍。而且樣式可能延及 到系統級別。這種樣式和資源之間的聯系是使用皮膚和主體的關鍵
6.2.1皮膚和主題
皮膚和主題都是控制UI外觀的技術。主題,是一種系統級別的外觀,例如 Windows2000的經典外觀,又如Windows XP的“Luna”主題。皮膚是一個特定於 應用程序的外觀,正如各種各樣具有不同樣式的媒體播放程序,例如WinApp和 Windows Media Player
皮膚和主題都可以在WPF實現,作為一組資源應用於需要該樣式的控件
既然皮膚的意圖在於控制一個特定應用程序的外觀,它將為標准控件提供更 多的樣式,可以在應用程序的指定部分定義各種各樣有命名的資源。例如,音樂 播放器可能使用一個ListBox用來顯示歌曲列表。皮膚可以為之提供一個特定的 外觀而不用影響應用程序中其他的ListBox。因此應用程序可以為這個ListBox設 置特定的命名的樣式,同時要在這個樣式中支持這個樣式。對於這種特定情形, 提供這樣一個樣式是可選擇的,但是在其他情形中,應用程序需要皮膚提供提供 指定的資源。例如,如果應用程序中有一個工具條,皮膚可能就需要提供資源並 在其中為這個工具條定義圖像。
同樣,主體是用於所有應用程序,因此,其必須為所有類型的控件提供模板 和樣式。比較而言,一個皮膚是特定於應用程序的,所以它不必提供廣泛全面的 一組樣式。如果應用程序並不使用每一個單獨的控件類型,皮膚只需要為那些在 應用程序中出現的控件提供樣式。示例6-20和示例6-21為一個相當簡單的皮膚, 展示了xaml和相應的後台代碼
示例6-20
<ResourceDictionary x:Class="SimpleSkin.BlueSkin"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Blue" />
<Setter Property="Foreground" Value="White" />
</Style>
</ResourceDictionary>
示例6-21
using System;
using System.Windows;
namespace SimpleSkin {
public partial class BlueSkin : ResourceDictionary {
public BlueSkin( ) {
InitializeComponent( );
}
}
}
以上代碼為一個按鈕設置了前景色和背景色。一個更復雜的皮膚應該可以為 更多的類型元素提供樣式,並且提供更多的屬性。更多的皮膚包括一些模板屬性 的設定,從而可以定義控件的外觀。但是即使是在這個簡單的例子中,底層的原 理也都是一樣的。示例6-22展示了一個UI,示例6-23則是這個UI的相應後台代碼 ,允許皮膚的切換。(這個示例假設有2個皮膚類,BlueSkin和GreenSkin,都是 使用示例6-20的技術定義的。)
示例6-22
<Window x:Class="SimpleSkin.Window1" Text="SimpleSkin"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">
<Grid Margin="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<RadioButtonList x:Name="radioSkins">
<TextBlock>Green</TextBlock>
<TextBlock>Blue</TextBlock>
</RadioButtonList>
<Button Grid.Row="1">Hello</Button>
</Grid>
</Window>
示例6-23
using System;
using System.Windows;
using System.Windows.Controls;
namespace SimpleSkin {
public partial class Window1 : Window {
public Window1( ) {
InitializeComponent( );
EnsureSkins( );
radioSkins.SelectionChanged += SkinChanged;
}
static ResourceDictionary greenSkin;
static ResourceDictionary blueSkin;
static bool resourcesLoaded = false;
private static void EnsureSkins( ) {
if (!resourcesLoaded) {
greenSkin = new GreenSkin( );
blueSkin = new BlueSkin( );
resourcesLoaded = true;
}
}
private void SkinChanged(object o, SelectionChangedEventArgs e) {
switch (radioSkins.SelectedIndex) {
case 0:
Application.Current.Resources = greenSkin;
break;
case 1:
Application.Current.Resources = blueSkin;
break;
}
}
}
}
SimpleSkin類的代碼確保了皮膚只創建一次,而且簡單地更換皮膚——通過 設置應用程序資源字典,使之成為選中的皮膚。(第二個皮膚的源GreenSkin, 在這裡沒有顯示出來,看上去和示例6-20相同,只是用綠色取代了藍色。)樣式 和系統資源隨著資源的更換自動反映出來:當更改皮膚的時候更新所有有效的控 件,因此,這就是我們需要的代碼。圖6-5顯示了代碼效果。
圖6-5
這種切換皮膚的方式有一個小障礙。除了使用皮膚資源,在應用程序級別也 存儲了一些資源,那麼這些應用程序資源會在切換皮膚時丟失。現在,唯一的解 決方案是保證每一個皮膚包含一份應用程序級別的資源副本。最好的辦法是將這 些副本保存在一個單獨的類,並將副本和並到資源皮膚中。WPF當前的版本不支 持自動和並資源字典,WPF團隊的成員已經聲明,他們正在考慮更容易的處理辦 法在未來的發布版本中。目前,只能手動處理,正如示例6-24所示。
示例6-24
ResourceDictionary skinResources = new FooSkinResources( );
ResourceDictionary nonSkinAppResources = new DrawingResources( );
foreach (DictionaryEntry de in nonSkinAppResources) {
skinResources.Add(de.Key, de.Value);
}
如上,你可以將代碼添加到加載資源的方法中。在示例6-23中,你可以在 EnsureSkins方法中和並資源,將blue和green皮膚都和並到 nonSkinAppResources中。