Monday, March 22, 2010

A while ago I needed to have multiple resource dictionaries available to my Silverlight application. One would be the base library with a complete set of style resources for the application, and the others would be resource dictionaries for alternative styles. The application would choose which alternative style to use when it loaded, and would merge the alternative style into the base style resources. For example we may have an application that we only want to install once on the server, but it needs to have different corporate branding depending on which customer is using it

After reading up on how best to use the MergedDictionary, and reading about resource usage problems others were having, I decided I needed a solution that would meet the following goals:

Allow me to keep various styles in separate resource files

Work with Prism modules

Require the minimum of system resources

Play nice with Blend (well, as much as possible)

There were trade-offs among the last two goals, but the following technique has been useful in my day to day development and I have continued to use it successfully.

I'll start with the basics though:

Reusable Styles

This first section describes an approach to styling that allows for separation of design and functionality, is efficient in terms of resource requirement, and is compatible with working with Blend.

Styling is carried out in a Silverlight application by defining a style and applying it to a control. For example, a TextBox control could be declared like this:

However, if the UserControl contains several TextBox controls that must all look the same it becomes tedious to update each one individually to make its appearance the same; and if the design requirements change then updating each control is even more tedious. So the attributes can be extracted out into a style like this:

This allows us to set the style on all TextBox controls in the UserControl to the “TextBoxStyle” style and update them all by changing the style in a single place. However, what if we want to apply this style to many TextBox controls throughout the application? If we re-declare the style in every UserControl then once again, if the design requirements change, we must tediously go through all declarations and update it once. We can do this by declaring the style in the App.xaml file.

3: <!-- Resources scoped at the Application level should be defined here. -->

4: <Style x:Key="TextBoxStyle" TargetType="TextBox">

5: <Setter Property="Width" Value="300"/>

6: <Setter Property="BorderBrush" Value="Blue"/>

7: <Setter Property="FontSize" Value="12"/>

8: <Setter Property="Background" Value="Black"/>

9: <Setter Property="Foreground" Value="White"/>

10: </Style>

11: </Application.Resources>

12: </Application>

Merged Dictionaries

Now we can use it in all UserControls in our project. However, if we need to present the same application with different style themes – for example different corporate branding of the same application – then we have a problem. We need to be able to swap the styles for different customers, but we don’t want to have to edit App.xaml every time and compile a specific version for that customer. The solution is create a separate style assembly that we import in App.xaml. In the style assembly we just have the XAML file(s) for a customer’s branding; the style may be broken up into a file for color definitions, layout, and templates for example, or it may all be in one XAML file. The Build Action for the XAML file(s) will be set to “Resource”.

To use the style in our main app, and our UserControls, we add a reference to our styles project and use the MergedDictionary to make it available as a resource, which looks like this:

In the example above, App.xaml loads in the BaseStyles.xaml and merges its content into the main shell’s resource library. This means that any control that the shell instantiates can apply one of the colors/templates/styles defined in BaseStyles.xaml. We could also have multiple style projects (with the same compiled assembly name) that we could swap in and out for different customer skins.

If we have other Prism modules that contain UserControls then we need to add a reference to the styles assembly in those modules also – but we should set the “Copy Local” property for that assembly reference to false since the Shell will already contain that styles assembly.

Resource Hog vs Design Time Support

Our UserControls don’t need to define any styles at all if they are all defined in the styles assembly and merged into App.xaml. We can explicitly import the styles assembly into each user control using exactly the same syntax as shown above for App.xaml, however each time we do that, we are creating another ResourceDictionary with the same content defined. Which means if we have 100 styles defined in BaseStyles.xaml, and we have 50 UserControls each importing BaseStyles.xaml into a ResourceDictionary, that means our application, at run time, has 50 ResourceDictionaries instantiated with a combined total of 5,000 style static resources.

That’s unacceptable so we don’t want to import the resource dictionary in each UserControl, however we still need some way of including it for design time in Blend. At run time the App.xaml ResourceDictionary will be available to all the UserControls that are loaded into the application from various modules, but at design time the UserControls themselves don’t contain any reference to the styles so all controls appear in their default state. Worse than that – some UserControls won’t even display properly in Blend if they use child UserControls that need styles from the styles library.

There is no easy solution to this problem – if we add the explicit MergedDictionary entry (as above) for each of our UserControls then we have design time support, but then we face excessive resource usage. If we don’t have the MergedDictionary declaration in each UserControl then we are keeping our resource usage low, but lose design time support.

One way around this is to have the ResourceDictionary declaration in each user control, but commented out. The declaration can be uncommented while the UserControl is being worked on in Blend, and then uncommented when the design work is complete.

Finally, we get to the bit about merging selected styles when the application loads.

ThemeMerger

The ThemeMerger class described below provides functionality that allows the merged styles to be dynamically handled at start-up. The example in the introduction was that

we may have an application that we only want to install once on the server, but it needs to have different corporate branding depending on which customer is using it. If we have a different virtual directory on the server for each customer then they can have a different URL, or we could create a separate ASPX/HTML page for each customer. With either of these techniques we have the ability to determine at run time which customer we need to style the application for, so we need some way to respond to that knowledge and merge the correct styles. Let’s take the example where we have a separate page for each customer. The page can pass a parameter into the Silverlight application identifying the customer (relevant part emphasized):

We want to use this knowledge at the earliest possible moment in the App.xaml since we want our resource dictionaries merged correctly before any XAML has been instantiated into controls. So in the App.xaml we can place the following code in the constructor:

So now we need some mechanism of merging the correct style; which is what the ThemeMerger provides for us. ThemeMerger gives us this mechanism with two steps. The first step is to use an attached dependency property named “SourceLocation” that is designed to be attached to a ResourceDictionary like this:

The second step for using the ThemeMerger is to tell it which XAML file to load from the specified namespace. We do this back in the App.xaml.cs constructor, using the value of the parameter we passed in from the html page, setting it to the ThemeMerger.ThemeName static property:

By the time the constructor gets down to the call to “InitializeComponent()”, the application has the correctly merged styles in place. The ThemeMerger has set the Source property on our second ResourceDictionary declaration by combining the namespace with the theme name.

There are two important points worth highlighting here:

The value of the “customer” parameter in the initParams is the same as the name of the xaml style filename.

Do not include the “.xaml” extension in the initParams, the ThemeMerger will add that.

The last step in the example above is to change the App.xaml file to refer to the StyleResources MergedStyle.xaml file:

We can leave in the commented out specific declarations for each style file and uncomment them when we need to use Blend to design the UserControls in the ShellView module, and place the following, similar declaration in each UserControl:

1: <UserControl >

2: <UserControl.Resources>

3: <ResourceDictionary>

4: <!--

5: This ResourceDictionary.MergedDictionaries tag is not required for the view to render correctly - the

6: App.xaml file links to the external style libraries and allows overriding styles for different views.

7: However, to edit this view in Expression Blend, you can uncomment the appropriate resource dictionary

8: that you want to work on (or both if overriding parts). See StyleResources.MergedStyles.xaml for more details.