Silverlight 3.0 moved its featureset closer to that of WPF by adding support for merged Resource Dictionaries and Style Inheritance. Without these features, developing custom templates and styles for Silverlight controls can become a bit of a copy-and-paste nightmare. Since I have used the implicit theming feature built into the Silverlight Toolkit to make my Silverlight controls fit into the overall look and feel of this site, I was hoping that these features would enable me to refactor my themes developed for Silverlight 2.0 to be a bit less unwieldy.
Silverlight toolkit themes do not support merged dictionaries
Unfortunately, merged dictionaries are not supported by the Silverlight Toolkit’s Themes feature – at least not as it stands. For example, the following XAML and accompanying class causes a XamlParseException if you try to use it.
public class MergedTheme : Theme {
public MergedTheme() :
base(typeof(MergedTheme).Assembly, "MergedTheme.Theme.xaml") {
DefaultStyleKey = typeof(MergedTheme);
}
}
All is not lost however, since full source code is provided for the Silverlight Toolkit, so it becomes a straightforward matter to track down the problem and supply a patch.
Writing a test to reproduce the problem
Since the toolkit comes with an accompanying test suite, we proceed in textbook TDD fashion by first writing a test that exercises the issue. We can reuse some of the existing code to do this, adding a resource dictionary containing a merged dictionary reference and adding the following test to the ImplicitStyleManagerTest class
///
/// Test that styles contained in a merged dictionary can be successfully applied
///
[TestMethod]
[Asynchronous]
[Description("Test that a dictionary contining merged dictionaries can be successfully loaded.")]
public void TestResourceDictionaryWithMergedDictionaries() {
Uri uri = new Uri("System.Windows.Controls.Testing.Theming;
component/ImplicitStyleManager/MergedResourceDictionary.xaml", UriKind.Relative);
TestAuto( (stackPanel) => {
SetResourceDictionaryUri(stackPanel, uri);
ImplicitStyleManager.SetApplyMode(stackPanel, ImplicitStylesApplyMode.Auto);
}, Colors.Blue);
}
The test fails as we would expect.
Fix the code to make the test pass
The Theme class works by transforming the ResourceDictionary xaml into a ContentControl. A quick glance at the xaml generated by this process in the Visual Studio debugger shows why an exception is being raised.
The ResourceDictionary.MergedDictionaries element is missing its parent ResourceDictionary element, so we add a couple of lines of code to ResourceParser.ParseElement to detect the element and wrap it in a ResourceDictionary, using LINQ to XML.
private static void ParseElement(XmlReader reader, XmlWriter writer, bool checkTypes) {
if (reader.Depth == 0) {...}
else {
// handle merged dictionaries by wrapping them in a ResourceDictionary element
if (reader.Name == "ResourceDictionary.MergedDictionaries") {
XElement parent = new XElement("ResourceDictionary");
parent.Add(XElement.ReadFrom(reader));
writer.WriteRaw(parent.ToString());
}
else { ... }
}
}
Having made the change, we rerun the test.
As we would hope, our custom theme now works as expected and we also have a unit test that ensures the bug does not reoccur. Note the Xaml code above, in that the Resource Dictionary is referenced using Pack Uri syntax. This is because Button.xaml is compiled as a Resource, rather than Content, since the latter doesn’t appear to work reliably and can result in obfuscated error messages such as XamlParseException: attribute “Button.xaml” value out of range.
I have submitted the patch for this fix to the toolkit site on Codeplex.
