Contents
Introduction
In this article, we'll see how to create a truly dynamic Silverlight themeable application. In order to be able to build the companion project,
you'll have to meet the following requirements:
- Microsoft Visual Studio 2010 installed.
- Silverlight Toolkit from the CodePlex website.
- Latest Silverlight 4 themes (JetPack, Accent Color, Windows 7, and Cosmopolitan) from
Microsoft Downloads.
We will test dynamic themes on a page like in the screenshot below:
How to dynamically change themes
We want to use the already existing toolkit themes to let users easily change the application look and feel. There are two ways of using existing toolkit themes:
- Reference the built-in themes
- Embed raw themes into the project
1. Reference the built-in themes
This technique has the following important advantage: you'll only need to reference the theme assemblies and controls (Core, SDK, or Silverlight Toolkit)
you are using. Different controls come in different assemblies, but you are not forced to load them all - only the ones you are using. This results in
a smaller application and easier to navigate project. The drawback is that there is no way to modify built-in themes. If you intend to create a custom control
or you want to adjust the look of an existing one, you can't. Or, you can by overriding a specific control theme with an explicit style - not a dynamic way anymore.
There are many articles on the web describing how to use the built-in themes. We will not stop on this here, but we'll skip to the second technique.
2. Embed raw themes into the project
Using this approach will bring us full control over existing Silverlight themes. To accomplish this, we'll have to add raw Silverlight themes
to the project. The main problem with this method is that you'll have to reference assemblies containing controls present in the embedded raw themes.
For small applications, this can be unacceptable, but for larger ones, most of the control assemblies are already referenced and no overload will be added.
New Silverlight 4 themes come in a nice structure, with six self-explanatory files per theme:
- Brushes.xaml (theme brush definitions)
- Fonts.xaml (theme font definitions)
- CoreStyles.xaml (core Silverlight controls like
Button
, TextBox
, ComboBox
, ListBox
...)
- SDKStyles.xaml (
TabControl
, TreeView
, DataGrid
, DatePicker
...)
- ToolkitStyles.xaml (
Accordion
, ContextMenu
, Chart
, BusyIndicator
...)
- Styles.xaml (styles used for single theme application layout)
Additionally, we have added CustomStyles.xaml at the same theme level and use it to define styles for our custom controls (in this article, just a page background control).
For each theme, there is a container (a MergedDictionaries
) with the above resources embedded as ResourceDictionary
s.
Below is a snapshot of the AccentColor.xaml content:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/SLDynamicThemes2;component/Assets/Themes/AccentColor/CoreStyles.xaml" />
<ResourceDictionary Source="/SLDynamicThemes2;component/Assets/Themes/AccentColor/SDKStyles.xaml" />
<ResourceDictionary Source="/SLDynamicThemes2;component/Assets/Themes/AccentColor/ToolkitStyles.xaml" />
<ResourceDictionary Source="/SLDynamicThemes2;component/Assets/Themes/AccentColor/Styles.xaml" />
<ResourceDictionary Source="/SLDynamicThemes2;component/Assets/Themes/AccentColor/CustomStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
To dynamically change themes at run-time, we will make use of the toolkit Theme
control. There are two options that can be chosen,
depending on the application's requirements. First one - easiest and straightforward - was suggested
by Michael Epner in article comments and is applicable when the whole application theme
is supposed to be changed. It is also more elegant because there is no need to use the ChildWindow
workaround initially presented as the only way to theme
child windows. The second approach - original article - has to be used when partial application theming is required (i.e., not the whole application, but only part(s) of it).
Below you can find both options.
2.a. Full application theming
The easiest solution is to make use of the Theme.SetApplicationThemeUri
static function that will take care of changing the whole application theme,
without the need to apply additional workarounds. There is no need to include the Theme
control to the MainPage
layout root or to create
any custom ChildWindow
to enable child window theming. Just call Theme.SetApplicationThemeUri
and the new look will be instantly visible.
void ThemeButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
if (button.Tag != null)
{
string themeName = button.Tag.ToString();
if (!String.IsNullOrEmpty(themeName))
{
Uri themeUri = new Uri(string.Format(@"/SLDynamicThemes2;component/" +
s@"Assets/Themes/{0}", themeName), UriKind.Relative);
Theme.SetApplicationThemeUri(App.Current, themeUri);
}
}
}
2.b. Partial application theming
To dynamically change themes at run-time, we will make use of the toolkit Theme
control and add it to the page(s) you want to apply Silverlight themes to.
In our sample, the Theme
control is added only to MainPage.xaml, enclosing the entire application layout within its borders.
<toolkit:Theme x:Name="ThemeContainer" ThemeUri="/SLDynamicThemes2;component/Assets/Themes/JetPack.xaml">
<controls:PageBackground HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<toolkit:DockPanel LastChildFill="True">
<StackPanel Orientation ="Horizontal" toolkit:DockPanel.Dock="Top"></StackPanel>
<Grid>
<application:Home x:Name="HomePage"/>
</Grid>
</toolkit:DockPanel>
</controls:PageBackground>
</toolkit:Theme>
The only thing we have to do in order to modify the theme is to change the ThemeContainer
’s ThemeUri
property to the desired one in MainPage.cs.
void ThemeButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
if (button.Tag != null)
{
string themeName = button.Tag.ToString();
if (!String.IsNullOrEmpty(themeName))
{
Uri themeUri = new Uri(string.Format(@"/SLDynamicThemes2;" +
@"component/Assets/Themes/{0}", themeName), UriKind.Relative);
ThemeContainer.ThemeUri = currentThemeUri = themeUri;
}
}
}
The four buttons on the top of the main page (see first screenshot) define the Tag
property that identifies the theme to be applied.
When clicked, the ThemeContainer
’s ThemeUri
will change accordingly and the new theme will be applied to the whole application.
Dynamic themes in ChildWindow
Before explaining everything about theming ChildWindow
controls, I have to include here an observation
of Liviu Catrina: right now, only the content of a ChildWindow
can be themed, not the child window appearance itself. There is a limitation in Silverlight Toolkit that prevents ChildWindow
control theming (even if raw themes contain
the style for child windows). You can read a short explanation provided by Justin Angel (one of the toolkit creators)
on the official Silverlight forums.
If you follow the first approach described above - 2.a. Full application theming, there is no need for further ChildWindow
tweaking.
Theme changes will be automatically applied to any child window the application is containing. For the second implementation - 2.b. Partial application theming,
we will have to prepare our child windows in order to make them themeable.
Even if we carefully enclose the application content inside the Theme
control, the ChildWindow
s will still remain outside it and changing
a theme will not be reflected in them. Next, we will present a solution that ensures that themes are applied also on a ChildWindow
(see the screenshot below):
ChildWindow
themes can be enabled by creating a custom class (CustomChildWindow
in our sample) where we will override the OnApplyTemplate()
function.
We will expect that all child windows derived from CustomChildWindow
will have a a Theme
control named ThemeContainer
.
The idea is to remember the current ThemeUri
of your application and use it when any child window is displayed.
public class CustomChildWindow : ChildWindow
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Theme themeContainer = (Theme)(this).FindName("ThemeContainer");
if (themeContainer != null)
{
App app = (App)App.Current;
MainPage mainPage = app.RootVisual as MainPage;
themeContainer.ThemeUri = mainPage.CurrentThemeUri;
}
}
}
When the newly created ChildWindow
shows up, we will check and apply the CurrentThemeUri
saved in the main page.
Below you can see the XAML code snippet of the child window we are using.
<toolkit:Theme x:Name="ThemeContainer">
<Grid x:Name="LayoutRoot" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<appControls:PageBackground HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Name="ThemeNameTextBlock" Loaded="ThemeNameTextBlock_Loaded"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Bottom">
<Button Name="OK" Click="Button_Click" Margin="0,0,10,0">OK</Button>
<Button Name="Cancel" Click="Button_Click" Margin="0,0,10,0">Cancel</Button>
</StackPanel>
</appControls:PageBackground>
</Grid>
</toolkit:Theme>
The ThemeContainer
control encloses everything in our child window and any child control will inherit the look and feel of the current application theme.
Conclusion
In this article, we detailed a method you can use to enable dynamic themes in your Silverlight applications. It gives you full control on existing themes
and lets you extend them with your own custom controls.
I want to say thank you to everyone for their pertinent suggestions and observations. Your help improved the original document and made it better.