65.9K
CodeProject 正在变化。 阅读更多。
Home

如何在 Silverlight 2 的 ResourceDictionary 中实现 MergedDictionaries

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (14投票s)

2008年12月19日

CPOL

3分钟阅读

viewsIcon

85013

downloadIcon

471

本文介绍如何在 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.xamlMyResourceDictionary2.xaml 包含将合并到主字典中的资源。Source 属性通过定义指向包含资源字典的文件的 URI 来定义位置。URI 由 pack URI 方案定义(请参阅 MSDN 文档)。

Silverlight 2 中的合并资源字典

由于 Silverlight 中没有名为 MergedDictionaries 的对应集合,因此必须开发自定义的合并功能。

但首先,让我们看看最终结果。

以下代码显示了示例项目中的 Generic.xaml 文件。该文件本身不定义任何样式,但它会将定义在 CustomControl1.xamlCustomControl3.xaml 中的资源字典的键合并到 Generic.xaml 字典中。字典 CustomControl1.xaml 又与资源字典 CustomControl2.xaml 合并。这就是为什么为第一个嵌入的字典定义了两个键 Parago.Windows.Controls.CustomControl1Parago.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 文件(字典)。

© . All rights reserved.