blog community
Silverlight 3: Loading Merged Resource Dictionaries runtime

To customize the look of our application in Silverlight 3, we organize everything “look related” in Styles and put those styles in ResourceDictionaries. Usually by adding those styles in XAML to the Resources property of the current user control. In Silverlight 2 we would get huge XAML files with huge ResourceDictionaries, because there wasn’t any way to put a ResourceDictionary in its own file. With Silverlight 3 and Merged Resource Dictionaries, we can remedy this problem by putting Styles in a ResourceDictionary which has it’s own .xaml file. That .xaml file needs to have a build action of “Content” or “Resource”. With both of these options the application needs to be recompiled every time you want to change the look of your application. With the solution provided below, you can change the look of your application by changing the .xaml file of the ResourceDictionary and just by pressing F5 (refresh) you will see the changes applied to your Silverlight application.

 

The sample application

Before we’re going to take a dive into the code, we’re first going to take a look at the example application:

   1: <UserControl x:Class="RuntimeStyles.MainPage"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   5:     mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
   6:   
   7:   <Grid x:Name="LayoutRoot">
   8:         <Grid.ColumnDefinitions>
   9:             <ColumnDefinition/>
  10:             <ColumnDefinition/>
  11:         </Grid.ColumnDefinitions>
  12:         <Ellipse Style="{StaticResource myEllipseStyle}" />
  13:         <Rectangle Style="{StaticResource myRectangleStyle}" Grid.Column="1"/>   
  14:   </Grid>
  15: </UserControl>

Nothing too fancy here: A rectangle and an ellipse which both have their Style properties set. The interesting part is that both of the styles come out of different .xaml ResourceDictionaries, without merging those dictionaries in the XAML shown above. Below are both of the styles:

   1: <ResourceDictionary
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   4:     <Style x:Key="myRectangleStyle" TargetType="Rectangle">
   5:         <Setter Property="Fill">
   6:             <Setter.Value>
   7:                 <SolidColorBrush Color="Blue"/>
   8:             </Setter.Value>
   9:         </Setter>
  10:         <Setter Property="Stroke">
  11:             <Setter.Value>
  12:                 <SolidColorBrush Color="Red"/>
  13:             </Setter.Value>
  14:         </Setter>
  15:     </Style> 
  16: </ResourceDictionary>
   1: <ResourceDictionary
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   4:     <Style x:Key="myEllipseStyle" TargetType="Ellipse">
   5:         <Setter Property="Fill">
   6:             <Setter.Value>
   7:                 <SolidColorBrush Color="Red"/>
   8:             </Setter.Value>
   9:         </Setter>
  10:         <Setter Property="Stroke">
  11:             <Setter.Value>
  12:                 <SolidColorBrush Color="Blue"/>
  13:             </Setter.Value>
  14:         </Setter>
  15:     </Style> 
  16: </ResourceDictionary>

Both of these styles are in their own .xaml files, relative to the location of the .xap file of my Silverlight application. They are not contained in my Silverlight project:

image

And a screen shot of the application:

image

The StyleLoader class

It’s good OO practice to give each class one responsibility,  so I’ve put the code to load the styles at runtime in its own class. This class is called “StyleLoader” and the source is shown below:

   1: using System;
   2: using System.Net;
   3: using System.Windows;
   4: using System.Windows.Markup;
   5: using System.Collections.Generic;
   6: using System.Linq;
   7: namespace RuntimeStyles
   8: {
   9:     public class StyleLoader
  10:     {      
  11:         public event EventHandler AllStylesLoaded;
  12:         private List<ResourceDictionary> _allReadyLoaded;
  13:         private ResourceDictionary _toMergeWith;
  14:  
  15:         public void LoadStyles(ResourceDictionary toMergeWith, Uri xamlUri, params Uri[] xamlUris)
  16:         {
  17:             if (_allReadyLoaded != null)
  18:             {
  19:                 throw new InvalidOperationException("You have to wait before" +
  20:                 " the previous call has finished!");
  21:             }
  22:             _allReadyLoaded = new List<ResourceDictionary>(xamlUris.Length);
  23:             _toMergeWith = toMergeWith;
  24:  
  25:             DownloadStyle(xamlUri);
  26:             foreach (Uri downloadUri in xamlUris)
  27:             {
  28:                 DownloadStyle(downloadUri);
  29:             }
  30:         }
  31:  
  32:         public void LoadStyles(Uri xamlUri, params Uri[] xamlUris)
  33:         {
  34:             LoadStyles(Application.Current.Resources,xamlUri, xamlUris);
  35:         }
  36:  
  37:         private void DownloadStyle(Uri downloadUri)
  38:         {
  39:             WebClient wc = new WebClient();
  40:             wc.DownloadStringCompleted += ParseAndAddStyles;
  41:             wc.DownloadStringAsync(downloadUri);
  42:         }
  43:  
  44:        private void ParseAndAddStyles(object sender, DownloadStringCompletedEventArgs e)
  45:        {
  46:            if (e.Error == null)
  47:            {
  48:                ResourceDictionary loaded = null;
  49:                try
  50:                {
  51:                    loaded = XamlReader.Load(e.Result) as ResourceDictionary;
  52:                }
  53:                catch
  54:                {
  55:                    CleanUp();
  56:                    throw;
  57:                }
  58:                if (loaded != null)
  59:                {
  60:                    if (_allReadyLoaded.Count == _allReadyLoaded.Capacity)
  61:                    {
  62:                        //This was the last call to complete
  63:                        _toMergeWith.MergedDictionaries.Add(loaded);
  64:                        foreach (ResourceDictionary dic in _allReadyLoaded)
  65:                        {
  66:                            _toMergeWith.MergedDictionaries.Add(dic);
  67:                        }
  68:                        CleanUp();
  69:                        OnAllStylesLoaded(new EventArgs());
  70:                    }
  71:                    else
  72:                    {
  73:                        _allReadyLoaded.Add(loaded);
  74:                    }
  75:                }
  76:                else
  77:                {
  78:                    CleanUp();
  79:                    throw new InvalidOperationException("The loaded xaml was not a resource dictionary!");
  80:                }
  81:            }
  82:            else
  83:            {
  84:                CleanUp();
  85:                throw e.Error;
  86:            }
  87:        }
  88:  
  89:        private void CleanUp()
  90:        {
  91:            _toMergeWith = null;
  92:            _allReadyLoaded = null;
  93:        }
  94:  
  95:         protected virtual void OnAllStylesLoaded(EventArgs args)
  96:         {
  97:             EventHandler temp = AllStylesLoaded;
  98:             if (temp != null)
  99:             {
 100:                 temp(this, new EventArgs());
 101:             }
 102:         }
 103:  
 104:     }
 105: }

I’ll only cover the important parts:

  • Line 15 –30: This method is the heart of the class. It accepts a resource dictionary to put the loaded styles in and one or more uri’s. These uri’s are the locations of the .xaml file you wish to load. The method is called LoadStyles, but can actually be used to load any ResourceDictionary. The method calls the DownloadStyle() method for each uri it receives, effectively creating a WebClient object for every uri. This means that all external ResourceDictionaries are downloaded in parallel. I keep track of how how many calls still have to complete by using the_allReadyLoaded list. You can not call this method again until all WebClient objects have completed their calls.
  • Line 32-35: This is just a convenience overload of above method. If you don’t want to supply a ResourceDictionary to load the styles in, the ResourceDictionary of the Application is used.
  • Line 44-87: This method is the completed event handler for the WebClient objects. Every time a WebClient call completes, the downloaded XAML string is parsed by using the XamlReader class and the resulting ResourceDictionary is added to the list of already loaded dictionaries. When the _allreadyLoaded list has used all of it’s capacity, the last WebClient request has returned and all the loaded dictionaries are merged and the AllStylesLoaded event is fired. I’m first gathering all the loaded dictionaries and then adding them in one go. This way, when one of the WebClient requests fail, I can leave the application in a consistent state with none of the resource dictionaries loaded. I thought about putting synchronization logic in this method, because all the WebClient calls are asynchronous, but the completed events of the different WebClient objects all fire on the UI thread. This means that the code in the ParseAndAddStyles method is executed by a single thread and no synchronization is needed.

  

Using the StyleLoader class

Below is the source of the MainPage file:

   1: using System;
   2: using System.Windows.Controls;
   3:  
   4: namespace RuntimeStyles
   5: {
   6:     public partial class MainPage : UserControl
   7:     {
   8:         public MainPage()
   9:         {
  10:             StyleLoader sl = new StyleLoader();
  11:             sl.AllStylesLoaded += new EventHandler(sl_AllStylesLoaded);
  12:             sl.LoadStyles(new Uri("Styles/MyEllipseStyles.xaml",UriKind.Relative),
  13:                 new Uri("Styles/MyRectangleStyles.xaml", UriKind.Relative));
  14:         }
  15:  
  16:         private void sl_AllStylesLoaded(object sender, EventArgs e)
  17:         {
  18:             InitializeComponent();
  19:         }
  20:  
  21:     }
  22: }

The one thing that’s really important is that the call to load the styles must happen before the InitializeComponent() call. This is very important because in the InitializeComponent() call all components execute their bindings. These will of course fail because no resource dictionaries have been loaded if InitializeComponent() is called before the StyleLoader raises it’s AllStylesLoaded event. You can find the full application here. Go ahead and change some of the styles in the ResourceDictionary .xaml files in the web project. You only have to press F5 (refresh) and you will see your changes applied to your Silverlight application without recompiling.


Posted 10-11-2009 9:14 by Alex van Beek

Comments

DotNetShoutout wrote Silverlight 3: Loading Merged Resource Dictionaries runtime - Alex van Beek
on 10-11-2009 9:38

Thank you for submitting this cool story - Trackback from DotNetShoutout

DotNetKicks.com wrote Silverlight 3: Loading Merged Resource Dictionaries runtime
on 10-11-2009 9:39

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Servefault.com wrote Silverlight 3: Loading Merged Resource Dictionaries runtime - Alex van Beek - blog community
on 10-11-2009 9:41

Thank you for submitting this cool story - Trackback from Servefault.com

Phil M wrote re: Silverlight 3: Loading Merged Resource Dictionaries runtime
on 17-11-2009 4:54

Is there a way to make this technique play nice with Blend?

Alex van Beek wrote re: Silverlight 3: Loading Merged Resource Dictionaries runtime
on 17-11-2009 9:14

No, you have to add all the Merged Dicttionaries at compile time to let blend display the effect of the dictionaries. Then, just before deployment of your app, you should remove the merging of the external dictionaries and load them in runtime.

Meykih wrote re: Silverlight 3: Loading Merged Resource Dictionaries runtime
on 26-11-2009 16:22

Nice stuff. But I would need some more: not only two independent resource dictionaries but one depending on the other. Means in one dictionary I would like to declare colors for styles in the second dictionary. Do you have an idea how to do that?

Alex van Beek wrote re: Silverlight 3: Loading Merged Resource Dictionaries runtime
on 27-11-2009 7:52

That wouldn't be too hard, but the StyleLoader above doesn't have direct support for it. You only have to make sure that first the dictionary that is depended on is loaded in, and at last the dictionary that depends on the first dictionary. You can use the StyleLoader for this in a work around kind of way. You first need to load the the dictionary that declares the colors using the StyleLoader. Than in the completed event, Load the dictionary that uses the colors. In the last completed event you need to call InitializeComponent().

Add a Comment

(required)  
(optional)
(required)  
Remember Me?
Enter code (required)
Powered by Community Server (Commercial Edition), by Telligent Systems