如何在 Silverlight 2 的 ResourceDictionary 中实现 MergedDictionaries






4.89/5 (14投票s)
本文介绍如何在 Silverlight 中为资源字典实现 MergedDictionaries 功能。
引言
Microsoft 的 Windows Presentation Foundation (WPF) 资源支持非常方便的合并资源字典功能。不幸的是,Silverlight 2 的资源不支持此功能。此功能提供了一种将资源定义和拆分到单独文件中的方法。
此功能对于开发 Silverlight 2 中的自定义控件也非常有用。目前,所有默认样式键都必须在 Themes/Generic.xaml 中定义。此文件可能很大,而合并资源字典是将样式定义拆分成可管理块的绝佳解决方案。
WPF 中的合并资源字典
在 WPF 中,资源字典的合并方式如下:
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MyResourceDictionary1.xaml"/>
<ResourceDictionary Source="MyResourceDictionary2.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
文件 MyResourceDictionary1.xaml 和 MyResourceDictionary2.xaml 包含将合并到主字典中的资源。Source
属性通过定义指向包含资源字典的文件的 URI 来定义位置。URI 由 pack URI 方案定义(请参阅 MSDN 文档)。
Silverlight 2 中的合并资源字典
由于 Silverlight 中没有名为 MergedDictionaries 的对应集合,因此必须开发自定义的合并功能。
但首先,让我们看看最终结果。
以下代码显示了示例项目中的 Generic.xaml 文件。该文件本身不定义任何样式,但它会将定义在 CustomControl1.xaml 和 CustomControl3.xaml 中的资源字典的键合并到 Generic.xaml 字典中。字典 CustomControl1.xaml 又与资源字典 CustomControl2.xaml 合并。这就是为什么为第一个嵌入的字典定义了两个键 Parago.Windows.Controls.CustomControl1
和 Parago.Windows.Controls.CustomControl2
的原因。
<ResourceDictionary
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:parago="clr-namespace:Parago.Windows.Controls;assembly=Parago.Windows.Controls">
<parago:ResourceDictionary.MergedDictionaries>
<parago:ResourceDictionary
Keys="Parago.Windows.Controls.CustomControl1,Parago.Windows.Controls.CustomControl2"
Source="/Parago.Windows.Controls;component/Themes/Controls/CustomControl1.xaml"/>
<parago:ResourceDictionary
Keys="Parago.Windows.Controls.CustomControl3"
Source="/Parago.Windows.Controls;component/Themes/Controls/CustomControl3.xaml"/>
</parago:ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
例如,XAML 文件 CustomControl1.xaml 的定义如下:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:parago="clr-namespace:Parago.Windows.Controls;assembly=Parago.Windows.Controls">
<parago:ResourceDictionary.MergedDictionaries>
<parago:ResourceDictionary
Keys="Parago.Windows.Controls.CustomControl2"
Source="/Parago.Windows.Controls;component/Themes/Controls/CustomControl2.xaml"/>
</parago:ResourceDictionary.MergedDictionaries>
<Style TargetType="parago:CustomControl1">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="parago:CustomControl1">
<Grid>
<TextBlock>CustomControl3</TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
自定义 ResourceDictionary
类内部调用 XamlReader.Load
方法。为了正确合并资源目录,必须完整定义命名空间,包括程序集部分;否则,将引发异常。
实现
自定义且实现该功能的类(也命名为 ResourceDictionary
)必须首先注册一个名为 MergedDictionaries
的新附加属性。定义和注册附加属性是一项简单的任务。
...
public static ResourceDictionary GetMergedDictionaries(DependencyObject d)
{
if(d == null)
throw new ArgumentNullException("d");
return (ResourceDictionary)d.GetValue(MergedDictionariesProperty);
}
public static void SetMergedDictionaries(DependencyObject d,
ResourceDictionary dictionary)
{
if(d == null)
throw new ArgumentNullException("d");
d.SetValue(MergedDictionariesProperty, dictionary);
}
public static readonly DependencyProperty MergedDictionariesProperty =
DependencyProperty.RegisterAttached(
"MergedDictionaries",
typeof(ResourceDictionary),
typeof(ResourceDictionary),
new PropertyMetadata(new PropertyChangedCallback(
OnMergedDictionariesPropertyChanged)));
static void OnMergedDictionariesPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ResourceDictionary dictionaryToMerge = e.NewValue as ResourceDictionary;
if(d is System.Windows.ResourceDictionary)
dictionaryToMerge.OnMergedDictionariesChanged((d as
System.Windows.ResourceDictionary));
}
protected virtual void OnMergedDictionariesChanged(
System.Windows.ResourceDictionary targetDictionary)
{
if(targetDictionary == null)
return;
if(string.IsNullOrEmpty(Keys))
throw new Exception("Keys property is not defined");
System.Windows.ResourceDictionary dictionaryToMerge = GetResourceDictionary();
// NOTE: Silverlight 2 does not provide an enumerator or iteration option
// for resource dictionaries
foreach(string key in Keys.Split(",".ToCharArray()))
{
string kv = key.Trim();
if(!string.IsNullOrEmpty(kv))
{
if(!dictionaryToMerge.Contains(kv))
throw new Exception(string.Format("Key '{0}' does not exist in " +
"resource dictionary '{1}'", kv, Source));
if(!targetDictionary.Contains(kv))
targetDictionary.Add(kv, dictionaryToMerge[kv]);
}
}
}
...
有关 Silverlight 中附加属性的更多信息,请参阅 MSDN 文档,或 SilverlightShow.net 网站上这篇精彩文章:Silverlight 中的附加属性。
命名空间 System.Windows.Controls
中的 Silverlight ResourceDictionary
类实现不提供枚举器。所有这些方法都会引发异常。因此,除了 Source
属性(见上文)之外,还需要定义一个名为 Keys
的新属性,类型为 String
。该值是要合并到主资源字典中的键的逗号分隔列表。
如果在合并的资源字典(例如 CustomControl1.xaml)中没有使用 x:Key
定义键名,则会使用包含命名空间(包括程序集部分)的类型名称。对于类型 CustomControl1
,键为 Parago.Windows.Controls.CustomControl1
。
OnMergedDictionariesChanged
方法(参见上面的列表)会在添加到 MergedDictionaries
集合的每个新定义的资源字典上被调用。该方法将以给定的标准 ResourceDictionary
对象实例的形式执行,该实例必须合并当前字典定义的键。
该方法调用辅助方法 GetResourceDictionary
(参见下面的列表)来解析 Source
属性中定义的 XAML 文件并将其转换为标准的 Silverlight ResourceDictionary
。然后,所有定义的键将被合并(添加)到名为 targetDictionary
的给定字典中。
以下列表显示了 GetResourceDictionary
方法的定义。
...
protected virtual System.Windows.ResourceDictionary GetResourceDictionary()
{
if(Source == null)
throw new Exception("Source property is not defined");
StreamResourceInfo resourceInfo = Application.GetResourceStream(Source);
if(resourceInfo != null && resourceInfo.Stream != null)
{
using(StreamReader reader = new StreamReader(resourceInfo.Stream))
{
string xaml = reader.ReadToEnd();
if(!string.IsNullOrEmpty(xaml))
return XamlReader.Load(xaml) as System.Windows.ResourceDictionary;
}
}
throw new Exception(string.Format("Resource dictionary '{0}' does not exist", Source));
}
...
就是这样。
摘要
总而言之,为当前版本的 Silverlight 2 实现合并资源字典功能很简单。此功能对于自定义控件开发尤其有用。本文的示例项目展示了如何将自定义控件开发所需的 Generic.xaml 文件拆分成多个 XAML 文件(字典)。