在 WPF 中,我們可以方便的在全局范圍定義一個樣式,就可以應用到所有這種類型的對象,這就是所謂的隱式樣式(implicit Style),比如:

WPF中定義樣式
<Window x:Class="WpfImplicitStyle.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.Resources>
<!-- 針對一種類型設置全局樣式 -->
<Style TargetType="Button">
<Setter Property="Background" Value="AliceBlue" />
</Style>
</Grid.Resources>
<StackPanel>
<Button>Button a</Button>
<Button>Button b</Button>
</StackPanel>
</Grid>
</Window>
這樣之后,兩個按鈕就都變成了淺藍色的背景。
但是在 Silverlight 里沒有辦法這樣做。我們必須手工對每一個需要設置樣式的控件添加 Style="{StaticResource someStyle}" 這樣的語句,挨個設置,非常麻煩。
好在 Silverlight Toolkit 里提供了一個類似的實現,叫做 ImplicitStyleManager (隱式樣式管理器,可以簡稱 ISM)。
該類的使用方法,是在某個根元素上設置一個附加屬性(Attached Property),然后,該元素下屬的視覺樹里符合特定類型的子元素的樣式,就可以被自動應用隱式樣式了。
例子如下:

Code
<UserControl x:Class="ImplicitStyleTest.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300"
xmlns:theming="clr-namespace:Microsoft.Windows.Controls.Theming;assembly=Microsoft.Windows.Controls.Theming">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="AliceBlue" />
</Style>
</Grid.Resources>
<!-- 在根元素上設置一次就可以了 -->
<StackPanel theming:ImplicitStyleManager.ApplyMode="Auto">
<Button Content="Button a"></Button>
<Button Content="Button b"></Button>
</StackPanel>
</Grid>
</UserControl>
運行一下例子試試就會發現,兩個按鈕的樣式都被設置了,這樣就實現了類似 WPF 里的隱式樣式行為。
在這個例子里可以看到,ApplyMode 屬性被設置成了 Auto. 其實它一共有3個可選值,分別代表如下含義:
1. Auto
每當 layout updated 的時候,ISM 會重新應用隱式樣式。在這種模式下如果元素在以后被動態的加入到視覺樹中, 它們將被應用隱式樣式。
需 要注意的是,LayoutUpdated 事件發生的非常頻繁,并且不光是當你添加元素后才發生。而我們又沒有類似 ItemAddedToTree 的事件,如果視覺樹比較大的話,ISM 遍歷它的時候就會花費比較多的時間,這可能給性能帶來一定的影響。但是為了方便,這里只好做一些折中的權衡,犧牲一點性能。如果視覺樹很大的時候,可以考 慮改用 OneTime 模式。
2. OneTime
僅在第一次加載時起作用,對后面動態加到 visual tree 里的元素不起作用。
有 時候,你的視覺樹很大,所以你不考慮用 Auto 模式。這時候你可以用 OneTime 模式一次性應用樣式;同時,需要在添加新節點之后,在代碼里手工調用 ISM 的 Apply 方法,這樣可以重新應用一次樣式。這樣的辦法可以避免 Auto 模式的一些性能損失。
3. None
效果跟沒設置 ApplyMode 屬性一樣。
了解了 ISM 如何使用,我們來看看它是怎么實現的。
我們知道,Silverlight 元素里面的 Style 在運行時只能被設置一次,否則就會出錯,ISM 也不例外,也要受這個制約。
ISM 的實現原理大致如下:
1. 定義一個叫做 ApplyMode 的附加屬性(Attached Property),提供給需要設置樣式的“根”元素使用。
而我 們知道,附加屬性可以在 xaml 里被設置,這就像上面的例子里所寫的那樣;同時,它有一個最大的好處,就是可以定義屬性改變時觸發的回調函數(注冊時定義在 PropertyMetadata 里面)。這樣,當我們在代碼里設置了 ApplyMode 后,ISM 就能觸發這個回調函數進行處理了。
2. 在這個回調函數中,注冊元素的 LayoutUpdated 事件處理函數,這樣,在該元素不管因為什么原因更新其 layout 的時候,就能夠得到通知。
這里最巧妙的地方是:將元素 LayoutUpdated 事件的處理委托以依賴屬性(DependencyProperty) 的形式存在該元素自身的屬性中,這樣就省去了自行管理很多 event handler 的煩惱了。。。依賴屬性真的是個好東西啊!
代碼:

Code
/// <summary>
/// ApplyModeProperty property changed handler.
/// </summary>
/// <param name="dependencyObject">FrameworkElement that changed its
/// ApplyMode.</param>
/// <param name="eventArgs">Event arguments.</param>
private static void OnApplyModePropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
FrameworkElement element = dependencyObject as FrameworkElement;
if (element == null)
{
throw new ArgumentNullException("dependencyObject");
}
ImplicitStylesApplyMode oldMode = (ImplicitStylesApplyMode)eventArgs.OldValue;
ImplicitStylesApplyMode newMode = (ImplicitStylesApplyMode)eventArgs.NewValue;
ImplicitStyleManager.SetHasBeenStyled(element, false);
EventHandler eventHandler = ImplicitStyleManager.GetLayoutUpdatedHandler(element);
// If element is automatically styled (once or always) attach event
// handler.
if ((newMode == ImplicitStylesApplyMode.Auto || newMode == ImplicitStylesApplyMode.OneTime)
&& oldMode == ImplicitStylesApplyMode.None)
{
if (eventHandler == null)
{
eventHandler =
(sender, args) =>
{
ImplicitStyleManager.PropagateStyles(element, false);
};
ImplicitStyleManager.SetLayoutUpdatedHandler(element, eventHandler);
element.LayoutUpdated += eventHandler;
}
}
else if ((oldMode == ImplicitStylesApplyMode.Auto || oldMode == ImplicitStylesApplyMode.OneTime)
&& newMode == ImplicitStylesApplyMode.None)
{
if (eventHandler != null)
{
element.LayoutUpdated -= eventHandler;
ImplicitStyleManager.SetLayoutUpdatedHandler(element, null);
}
}
}
3. 在上述 LayoutUpdated 的事件處理函數中,遍歷控件的視覺樹,對符合條件的元素設置 Style(也只能設置一次)。
這里值得一說的是遍歷樹的代碼技巧,為了避免遞歸或者類似方法遍歷樹造成的開銷,這里實際使用了一種很巧妙的 Stack 來訪問樹節點。并且,在所有需要遍歷的地方,盡可能的使用了 yield return, 以一種函數式編程的寫法來延遲實際對節點的操作。
具體代碼不細細解釋了,這里把 MS 的代碼貼來僅供欣賞一下 Functional Programming,有興趣的朋友可以自己研究:

Code
/// <summary>
/// This method propagates the styles in the resources associated with
/// a framework element to its descendents. This results in a
/// style inheritance that mimics WPF's behavior.
/// </summary>
/// <param name="element">The element that will have its styles
/// propagated to its children.</param>
/// <param name="recurse">Whether to recurse over styled elements that
/// are set to OneTime and have already been styled.</param>
private static void PropagateStyles(FrameworkElement element, bool recurse)
{
BaseMergedStyleDictionary initialDictionary = GetMergedStyleDictionary(element);
// Create stream of elements and their base merged style
// dictionaries by traversing the logical tree.
IEnumerable<Tuple<FrameworkElement, BaseMergedStyleDictionary>> elementsToStyleAndDictionaries =
FunctionalProgramming.Traverse(
new Tuple<FrameworkElement, BaseMergedStyleDictionary>(element, initialDictionary),
(elementAndDictionary) =>
elementAndDictionary
.First
.GetLogicalChildrenDepthFirst()
.Select(childElement =>
new Tuple<FrameworkElement, BaseMergedStyleDictionary>(
childElement,
new MergedStyleResourceDictionary(
ImplicitStyleManager.GetExternalResourceDictionary(childElement) ?? childElement.Resources,
elementAndDictionary.Second))),
(elementAndDictionary) => recurse ||
(ImplicitStyleManager.GetApplyMode(elementAndDictionary.First) != ImplicitStylesApplyMode.OneTime ||
!ImplicitStyleManager.GetHasBeenStyled(elementAndDictionary.First)));
foreach (Tuple<FrameworkElement, BaseMergedStyleDictionary> elementToStyleAndDictionary in elementsToStyleAndDictionaries)
{
FrameworkElement elementToStyle = elementToStyleAndDictionary.First;
BaseMergedStyleDictionary styleDictionary = elementToStyleAndDictionary.Second;
bool styleApplied = false;
if (elementToStyle.Style == null)
{
Style style = styleDictionary[GetStyleKey(elementToStyle)];
if (style != null)
{
elementToStyle.Style = style;
styleApplied = true;
}
}
if (ImplicitStyleManager.GetApplyMode(elementToStyle) == ImplicitStylesApplyMode.OneTime && (VisualTreeHelper.GetChildrenCount(elementToStyle) > 0 || styleApplied))
{
ImplicitStyleManager.SetHasBeenStyled(elementToStyle, true);
}
}
}