Notes on Software

Shared ResourceDictionary for Silverlight

Posted by K. M. on April 5, 2011

The problem:

Both WPF and Silverlight support merged ResourceDictionary usage via the MergedDictionaries property.  Typically this is done with markup similar to the following in App.xaml

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Shared.xaml"/>
                <ResourceDictionary Source="Resources/Views1.xaml"/>
                <ResourceDictionary Source="Resources/Views2.xaml"/>
                <ResourceDictionary Source="Resources/Views3.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>

In WPF any resources defined within Shared.xaml are accessible by a StaticResource reference from any of the other dictionaries because of the order in which they are merged into App.xaml. In Silverlight, all the dictionaries are loaded independently and StaticResource references across dictionaries do not work. To get the references to work, Shared.xaml has to be merged into each of the other dictionaries. However, this means that the contents of Shared.xaml will be loaded and duplicated at each place where it is merged in. This is clearly bad. While searching for a solution I came across this post by Christian Mosers. Mosers defines a SharedResourceDictionary class deriving from ResourceDictionary and redefines the Source property where he uses some caching logic to avoid reloading the dictionary. Unfortunately this solution does not work in Silverlight. For some reason, Silverlight does not allow the same dictionary instance to be merged into multiple dictionaries. Additionally, the Visual Studio designer throws an exception in the setter of the Source property. Apparantly Visual Studio does not like derived classes.

The solution:

The following class solves both the problems detailed above

public class SharedResourceDictionary : ResourceDictionary
{
    public static Dictionary<Uri, ResourceDictionary> _sharedDictionaries =
       new Dictionary<Uri, ResourceDictionary>();

    private Uri _sourceUri;
    public new Uri Source
    {
        get { return _sourceUri; }
        set
        {
            _sourceUri = value;
            if (!_sharedDictionaries.ContainsKey(value))
            {
#if SILVERLIGHT
                var dict = new ResourceDictionary();
                Application.LoadComponent(dict, value);
#else
                var dict = (ResourceDictionary)Application.LoadComponent(value);
#endif
                _sharedDictionaries.Add(value, dict);
            }
            CopyInto(this, _sharedDictionaries[value]);
        }
    }

    private static void CopyInto(ResourceDictionary copy, ResourceDictionary original)
    {
        foreach (var dictionary in original.MergedDictionaries)
        {
            var mergedCopy = new ResourceDictionary();
            CopyInto(mergedCopy, dictionary);
            copy.MergedDictionaries.Add(mergedCopy);
        }
        foreach (DictionaryEntry pair in original)
        {
            copy.Add(pair.Key, pair.Value);
        }
    }
}

    This bypasses the ResourceDictionary.Source property entirely by using Application.LoadComponent directly, thus keeping the Visual Studio designer happy. It copies the cached ResourceDictionary to keep Silverlight happy. This is slightly less efficient than merging in the same cached instance everywhere but it is certainly much better than reloading every resource multiple times. The only catch is that the Source property must be set in a way that Application.LoadComponent can resolve it correctly. I use this with absolute URIs.

Sample usage:

Markup similar to the following can be used at the beginning of each resource dictionary that needs to reference resources in Shared.xaml

    <ResourceDictionary.MergedDictionaries>
        <ui:SharedResourceDictionary Source="/my_assembly_name;component/Resources/Shared.xaml"/>
    </ResourceDictionary.MergedDictionaries>
About these ads

15 Responses to “Shared ResourceDictionary for Silverlight”

  1. Vinicius Sanches said

    Hi KM, I tried adding your code in my application but could not. I have a project called Glaucus.Temas where is my class SharedResourceDictionary.cs as I copied the code posted by you.
    In my other project called Glaucus.Portal App.xaml in my file I put the following code

    my file has the following code PortalResources.xaml

    .. / Images / cadeado.png

    In my file in the following piece of code below:

    it does not recognize the image, but if I switch to normal ResourceDictionary everything works, what might be happening? Will Silverlight 5 will solve this problem without adding this implementation?

    thanks for help

    Vinicius

    • K. M. said

      Hi Vinicius,
      As I mentioned in the post, I use Application.LoadComponent to load the resource dictionary and one consequence of that is that relative URI’s are not supported. Refer to your image using an absolute URI and things should work fine. For example

          <Setter Property="Source" Value="/your_assembly_name;component/Images/cadaeo.png"/>
      

      Let me know if this does not work

      I don’t think Silverlight 5 will solve this, but I haven’t played with the beta yet so I cannot be sure.

  2. Vinicius Sanches said

    I could not add the xaml code in the post, how?

    Vinicius Sanches

  3. Rodrigo said

    Hi K.M,

    I tried to implement the SharedResourceDictionary as you suggested but I was getting an exception in the call to Application.LoadComponent,
    stating that nested calls to BeginInit() were not allowed.

    Somehow the setter of the Source property is invoked during initialization, so after “reflecting” the original ResourceDictionary I found out that I could simply wrap the call to Application.LoadComponent like this:

    EndInit(); //closes the current initialization scope
    Application.LoadComponent(this, value); //opens and closes another initialization scope
    BeginInit(); //reopens a scope so that we remain consistent to the situation we were in.

    Although that solved all issues at runtime, I still cannot make it work at design-time, neither in VS2010sp1 nor in Blend4.

    Now the designer throws an ArgumentException saying that absolute URIs are not supported by LoadComponent!!

    The only way around that is disabling the logic if in design mode, like this:

    private static bool IsInDesignMode
    {
    get
    {
    return (bool)DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty,
    typeof(DependencyObject)).Metadata.DefaultValue;
    }
    }

    public new Uri Source
    {
    get { return IsInDesignMode ? base.Source : _source; }
    set
    {
    if (IsInDesignMode)
    {
    try
    {
    base.Source = value;
    }
    catch (Exception)
    {
    }
    return;
    }

    …… omitted…..
    }
    }

    but of course now the designer can’t find any resource and the WYSIWYG experience is lost..

    Any idea on what’s going on?
    Does your code worked for you in design mode?

    Thank you very much and sorry for unformatted code :-)

    -Rodrigo-

    • Rodrigo said

      Oh, by the way, I’m doing this in a WPF 4 project, not in Silverlight… maybe they work differently?

    • K. M. said

      Hi Rodrigo,
      I have updated the post with the code that I currently use for both WPF and Silverlight. It has been a while since I last opened the designer in Visual Studio, but I think this does work with the designer in both WPF (v4) and Silverlight (v4). I haven’t tried Blend.

      If you use this, note that this does change the way StaticResource references are resolved in some subtle ways. As long as you always declare your resources before you refer to them, things work as they should. WPF sometimes allows you to get away with out-of-order usage (I don’t recollect the exact situation) but this solution disables that behavior.

      Please let me know if you come up with some case where this does not work.

      • Rodrigo said

        Hi KM, thank you for your time.

        I found out that Silverlight behaves slightly differently after posting my last comment.

        With your updated code I get the following results:

        WPF:
        – Runtime OK
        – VS designer crashes (ArgumentException in System.Windows.Application.LoadComponent(Uri resourceLocator), cannot use absolute URIs
        – Blend: same exception but it doesn’t crash the designer.

        Silverlight:
        – Runtime OK
        – VS designer is happy and display correct resources, although intellisense cannot find them (not a big concern)
        – Blend: no errors.

        In both situations Blend is unable to find the resources and asks for a design-time replacement of them… that’s weird.

        I think I will keep searching for a final solution. Maybe using some pre-compile event that scans xaml files and swaps regular mergedDictionaries used in the original markup with shared ones… not shure if it’s possible though.

        I’ll let you know if I find some solution.

        Ciao!

        -Rodrigo-

  4. K.E. said

    K.M.

    I’m attempting to use your solution as I’ve encountered much frustration in an attempt to abstract and modular-ize my style.xaml files.

    In the root project directory I have a “Styles” folder which has the common.xaml file for brushes and commonly used styles and also 4-5 style files to keep common styling grouped and separated from the rest. These any or all of these 4-5 style files might use a brush color (for instance) defined in common.xaml.

    I’ve successfully added the

    code to each of the 4-5 ResourceDictionary elements. Does this code ALSO need to go inside the Common.xaml or only those depenedent on key/definitions inside Common.xaml?

    Currently in My App.xaml File I have

    When I run the silverlight application I get the following exception in the app.xaml.cs InitializeComponent() call.
    Cannot find a Resource with the Name/Key fiberBrush [Line: 9 Position: 44] (this is defined in _Common.xaml

    Finally I am unsure on the buildAction and CopyToOutputDirectory properties of all the style files. Currently mine are set to Page and Always Copy (respectively).

  5. K.E. said

    Corrected previous post to include source code…

    I’ve successfully added the

        <ResourceDictionary.MergedDictionaries>
            <util:SharedResourceDictionary Source="/VSI.UI;component/Styles/_Common.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    

    code to each of the 4-5 ResourceDictionary elements. Does this code ALSO need to go inside the Common.xaml or only those depenedent on key/definitions inside Common.xaml?

    .
    .
    .
    Currently in My App.xaml File I have:

        <Application.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="Styles/_Common.xaml"/>
                    <ResourceDictionary Source="Styles/ChartBarSeriesDataPointStyle.xaml" />
                    <ResourceDictionary Source="Styles/ChartColumnSeriesDataPointStyle.xaml" />
                    <ResourceDictionary Source="Styles/ChartStyles.xaml" />
                    <ResourceDictionary Source="Styles/NavigationLinkStyles.xaml" />
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </Application.Resources>
    
    
  6. K.E. said

    Nevermind – I just got it all to work…. awesome, awesome code!!

    • K. M. said

      Good to know that you got it to work. Not sure what you were missing though. I have used pretty much the same pattern successfully with the xaml files with build action set to Page and Copy to Output Directory set to Do not copy.

  7. J.O. said

    One undocumented benefit of this approach is that it gives you a debugging step when trying to diagnose exceptions like the “Element is already the child of another element” exception. Great stuff!

  8. […] Inutile de dire que cette pratique est plutôt mauvaise d’un point de vue performance. K.M s’est basé sur cet article pour créer une classe fonctionnant en Silverlight […]

  9. Magnificent beat ! I wish to apprentice while you amend your site,
    how could i subscribe for a blog site? The account
    aided me a acceptable deal. I had been a little bit acquainted of
    this your broadcast provided bright clear concept

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: