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: >="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: >="http://schemas.microsoft.com/winfx/2006/xaml"
4: >="http://schemas.microsoft.com/expression/blend/2008" >="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: >="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: >="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: >="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: >="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:
And a screen shot of the application:
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.