在WPF中,资源字典的合并功能非常便捷,它允许开发者将资源定义分散到不同的文件中。然而,遗憾的是,Silverlight2并不支持这一特性。尽管如此,仍然可以通过自定义的方式来实现资源字典的合并。这对于开发自定义控件来说尤其有用,因为它可以将默认样式键分散到多个文件中,从而避免Themes/Generic.xaml
文件变得过于庞大。
在WPF中,资源字典的合并过程如下:
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MyResourceDictionary1.xaml" />
<ResourceDictionary Source="MyResourceDictionary2.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
在上述代码中,MyResourceDictionary1.xaml
和MyResourceDictionary2.xaml
文件包含了将被合并到主字典中的资源。Source
属性通过定义一个包URI来指定资源字典文件的位置。
在Silverlight 2中,由于没有名为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();
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的ResourceDictionary
类实现在System.Windows.Controls
命名空间中,并不提供枚举器。所有这些方法都会抛出异常。因此,除了Source
属性(见上文)之外,还需要定义一个名为Keys
的新属性,类型为String
。值是一个逗号分隔的键列表,这些键将被合并到主资源字典中。
如果在合并资源字典中没有使用x:Key
定义键名(例如,CustomControl1.xaml
),则使用包括命名空间的类型名称作为键。对于类型CustomControl1
,键是Parago.Windows.Controls.CustomControl1
。
方法OnMergedDictionariesChanged
(见上文)将为添加到MergedDictionaries
集合中的每个新定义的资源字典调用。该方法将使用给定的标准ResourceDictionary
对象实例执行,当前字典定义的键需要合并到这个字典中。
方法调用辅助方法GetResourceDictionary
(见下文)将解析并转换定义在Source
属性中的XAML文件为标准的SilverlightResourceDictionary
。然后,所有定义的键将合并(添加)到给定的字典中,称为targetDictionary
。
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));
}