Thursday, May 27, 2010

Dynamic Silverlight Theme Switcher

I was recently trying to determine how easy it would be to add theme changing functionality into my Silverlight apps. The Silverlight Toolkit provides 11 very nice looking themes, so it would be nice to give users the option to switch between themes.

After doing some research, I found that you can instantiate a theme, add content to the theme, and then add the theme as a child of another control. What I have done here is create a simple UserControl that has a StackPanel for showing the theme and a ComboBox for picking a theme. I have a separate page called Content that has some sample controls on it. This could be any page of your choosing and integrated with any project fairly easily. I have provided code summary, screenshots, and full code below.

Code Summary
When a theme is chosen, the following happens:

1. A Content control is instantiated.
Content c = new Content();
2. Based on what is chosen in a ComboBox, a Theme object is instantiated, and the Content object is added as the Content.
Theme t = new BubbleCremeTheme() { Content = c };
3. The Theme object is added as a child to a StackPanel on your main control.
ThemePanel.Children.Add(t);

Screenshots

Design View – MainPage.xaml
image
Design View – Content.xaml
image
Running - No Theme
image
Running - Picking a Theme
image
Running - Expression Dark Theme selected
image

Steps:

1. Create a Silverlight application in Visual Studio. I called mine “Theme Switcher”.
2. In the Web Application Project, add a folder to your ClientBin folder called “images”.
3. Add an image for each themes to this folder. You can make your own or get them from Silverlight Toolkit site. For simplicity, I renamed all of mine to the theme name. For example, “Rainier Orange.png”.
4. In the Silverlight application, add all of the assemblies as references. You can find these in the directory where you installed the Silverlight Toolkit. For example:
C:\Program Files\Microsoft SDKs\Silverlight\vX.X\Toolkit\MMMYY\Themes
5. Add a UserControl called Content.xaml and update the XAML and code behind as follows:
XAML
<UserControl x:Class="ThemeSwitcher.Content"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d"
d:DesignHeight="265"d:DesignWidth="400">
<
Gridx:Name="LayoutRoot"Height="265"Background="Transparent"Margin="5,5">
<
Grid.RowDefinitions>
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
</
Grid.RowDefinitions>
<
TextBlock Text="TextBlock"Grid.Row="0"Margin="3,3" />
<
ComboBox Grid.Row="1"Margin="3,3">
<
ComboBoxItem Content="ComboBoxItem1"IsSelected="True" />
<
ComboBoxItem Content="ComboBoxItem2" />
<
ComboBoxItem Content="ComboBoxItem3" />
<
ComboBoxItem Content="ComboBoxItem4" />
<
ComboBoxItem Content="ComboBoxItem5" />
</
ComboBox>
<
StackPanel Orientation="Horizontal"Grid.Row="2"Margin="3,3"VerticalAlignment="Center">
<
RadioButton Content="RadioButton1"Margin="3,3" />
<
RadioButton Content="RadioButton2"Margin="3,3" />
<
RadioButton Content="RadioButton3"Margin="3,3" />
</
StackPanel>
<
StackPanel Orientation="Horizontal"Grid.Row="3"Margin="3,3"VerticalAlignment="Center">
<
CheckBox Content="CheckBox1"Margin="3,3" />
<
CheckBox Content="CheckBox2"Margin="3,3" />
<
CheckBox Content="CheckBox3"Margin="3,3" />
</
StackPanel>
<
ListBox Grid.Row="4"Grid.RowSpan="2"Margin="3,3">
<
ListBoxItem Content="ListBoxItem1" />
<
ListBoxItem Content="ListBoxItem2" />
<
ListBoxItem Content="ListBoxItem3" />
<
ListBoxItem Content="ListBoxItem4" />
</
ListBox>
<
SliderGrid.Row="6"Margin="3,3" />
<
ButtonContent="Button"Grid.Row="7"Margin="3,3" />
</
Grid>
</
UserControl>
6. Update the MainPage XAML and code behind like the following:
XAML
<UserControl x:Class="ThemeSwitcher.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ThemeSwitcher"
mc:Ignorable="d"
d:DesignHeight="325" d:DesignWidth="400" Height="325" Width="400">
<
Border CornerRadius="5" BorderBrush="Gray" BorderThickness="1">
<
Grid x:Name="LayoutRoot" Margin="2,2">
<
Grid.RowDefinitions>
<
RowDefinition Height="270" />
<
RowDefinition Height="50*" />
</
Grid.RowDefinitions>
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="100" />
<
ColumnDefinition />
</
Grid.ColumnDefinitions>
<
Border CornerRadius="5" BorderBrush="Silver" BorderThickness="1"
Grid.Row="0" Grid.ColumnSpan="2" Margin="3">
<
StackPanel x:Name="ThemeDisplay" />
</
Border>
<
TextBlock Text="Select Theme:" FontWeight="Bold" Grid.Row="1" Grid.Column="0"
VerticalAlignment
="Center" Margin="3,3" />
<
ComboBox x:Name="ThemeSelector" Grid.Row="1" Grid.Column="1"
SelectionChanged
="ThemeSelector_SelectionChanged" Margin="3,3">
<
ComboBox.ItemTemplate>
<
DataTemplate>
<
StackPanel Orientation="Horizontal">
<
Image Source="{Binding ImageUrl}" />
<
TextBlock Text="{Binding ThemeTitle}" FontWeight="Bold" Margin="3,3,3,3"
VerticalAlignment
="Center" />
</
StackPanel>
</
DataTemplate>
</
ComboBox.ItemTemplate>
</
ComboBox>
</
Grid>
</
Border>
</
UserControl>
Code Behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Controls.Theming;
using System.Windows.Media;

namespace ThemeSwitcher
{
public classThemeSelectorInfo
{
public ImageSource ImageUrl { get; set; }
public string ThemeTitle { get; set; }
}
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
string[] themes = { "Bubble Creme", "Expression Dark",
"Expression Light", "Rainier Orange",
"Rainier Purple", "Shiny Blue",
"Shiny Red", "Twilight Blue"};
List<ThemeSelectorInfo> themeInfo =
(from t inthemes
select newThemeSelectorInfo()
{
ImageUrl = new System.Windows.Media.Imaging.BitmapImage(
new Uri("images/" + t + ".png", UriKind.Relative)),
ThemeTitle = t
}).ToList();
themeInfo.Insert(0,
new ThemeSelectorInfo() { ThemeTitle = "No Theme" });
ThemeSelector.ItemsSource = themeInfo;
ThemeSelector.SelectedIndex = 0;
}

private void ThemeSelector_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
Content c = new Content();
Theme t = new ExpressionDarkTheme();
if (ThemeDisplay != null)
{
ThemeDisplay.Children.Clear();
switch (((ThemeSelectorInfo)ThemeSelector.SelectedItem).ThemeTitle)
{
case "Bubble Creme":
t = new BubbleCremeTheme() { Content = c };
break;
case "Expression Dark":
t = new ExpressionDarkTheme() { Content = c };
break;
case "Expression Light":
t = new ExpressionLightTheme() { Content = c };
break;
case "No Theme":
break;
case "Rainier Orange":
t = new RainierOrangeTheme() { Content = c };
break;
case "Rainier Purple":
t = new RainierPurpleTheme() { Content = c };
break;
case "Shiny Blue":
t = new ShinyBlueTheme() { Content = c };
break;
case "Shiny Red":
t = new ShinyRedTheme() { Content = c };
break;
case "Twilight Blue":
t = new TwilightBlueTheme() { Content = c };
break;
}
if (t.Content != null)
{
ThemeDisplay.Children.Add(t);
}
else
{
c.Foreground = new SolidColorBrush(Colors.Black);
c.LayoutRoot.Background =
new SolidColorBrush(Colors.White);
ThemeDisplay.Children.Add(c);
}
}
}
}
}

Notes:
1. I have not included BureauBlue, BureauBlack, and WhistlerBlue as there is currently a bug related to XamlParseExceptions. I am using the 11/09 release of the Silverlight 3.0 Toolkit in this example, so this may not be a problem in the newer versions.
2. The Background property of the LayoutRoot grid is White by default. Whenever you use these themes, you should always change this to Transparent.
3. If you do add theme changing functionality, remember that each reference make to a theme assembly increased the size of your XAP file. You can find more information here:
How to: Use Application Library Caching
image

4 comments:

  1. Thanks. This is cleaner than using the ImplicitStyleManager solution(example can be had at: http://weblogs.asp.net/lduveau/archive/2009/11/17/skin-your-silverlight-3-app-with-implicit-style-manager.aspx).

    Being able to work with the dlls and not the xaml for styles is better for me and you get more options in how to load the files on the server to save space or for performance. I'll be using your solution as a stepping stone for my theme switcher.

    ReplyDelete
  2. Thanks man works like a charm. Just put a theme wrapper around my whole project

    ReplyDelete