Skinning a WPF Application

Hello again!

This time I’ll talk a little about skinning/theming a WPF application. I honestly believe this is an important feature for WPF application for a few reasons ;

1) It’s really easy to do so there is no excuse for not doing it

2) Visualization is supposed to be the strong suit of any WPF application

3) Everybody loves customization, especially if it’s a high-end user application ( like towel )

And one more key thing before going into technical stuff; style files should be external so users would be able to edit exiting styles or create new ones by just using notepad! Remember users would never bother with it if it’s not easy to do.

 

OK so here is our plan;

1) We’ll extract all style information to external xaml files and call those styles using Static or Dynamic Resource binding.

2) Search folders for any acceptable Theme/Skin folder

3) Put those Theme/Skin names in a combo box so users can easily change the theme

4) Add all new style entries to Application Resource pool ( and they’ll override the old entries )

Now if you use DynamicResource for style bindings, everything will be updated as soon as user selects a new item from combo box. If you use StaticResource though, you’ll have to save selection as a setting and load those style files next time user runs the application. ( remember “you need to restart the application for settings to take effect” thingy )

 

Let’s start, first of all you should extract every single style information from your page/window/usercontrol xaml to an external resource dictionary. I believe this is not only important for Theming but also for a better workflow since xaml’s tend to get VERY messy with style information inside.

Theme file structure;

WindowClipping (20)

And sample Page/Window/UserControl xaml;

<StackPanel Margin=“212,40,40,40” Orientation=“Vertical”>
    <Border Style=”{DynamicResource BorderStyle}>
        <TextBlock Style=”{DynamicResource TextStyle1} Text=“CheapTowel” />
    </Border>
    <Border Style=”{DynamicResource BorderStyle2}>
        <TextBlock Style=”{DynamicResource TextStyle2} Text=“Fluffy Towel” />
    </Border>
    <Border Style=”{DynamicResource BorderStyle3}>
        <TextBlock Style=”{DynamicResource TextStyle3} Text=“Glorious Towel” />
    </Border>
</StackPanel>

If you check that xaml sample, you’ll see that I extracted the style information from TextBlocks and Borders so they’ll be skinable but Margin and Orientation values of StackPanel are still static so we won’t be able to change those using skins.

I also haven’t included Text into styles as I’ll talk about Localization in a future post.

OK now this xaml means, those 3 Borders and 3 TextBlocks will check Application Resource pool for those particular style entry keys and use them. So every Theme should have style entries exactly by those names. If a textblock demands “TextStyle3”, it’ll go use the current theme’s “TextStyle3” entry. Easy peasy.

Now let’s populate our combo box with themes in our directory;

First, ThemeData helper class;

    public class ThemeData
    {
        public string ThemeName { get; set; }
        public string ThemePath { get; set; }
    }

And the rest;

var themes =
    from dir in
        Directory.GetDirectories(Directory.GetParent(Assembly.GetEntryAssembly().Location) + “/Themes/”)
    select new ThemeData()
                {
                    ThemeName = new FileInfo(dir).Name,
                    ThemePath = dir
                };

foreach (ThemeData td in themes)
{
    combo.Items.Add(td);
}

As you can see, we look for “Themes” folder, which expected to be on the same level as our executable. There isn’t a existence check but you can add those stuff as you wish. Notice we’re looking for directories inside “Themes” folder, not files since we’ll have folders for different Themes and then theme files in those folder. Guess I should have talked about file structure before eh? Anyway, we need just folder names and paths for combo box and we’re saving all those into ThemeData helper class.

Now that we’ve populated the combo box with our Themes, all we have to do now, is creating a “SelectionChanged” event and add selected theme’s files to the application pool.

    private void ThemeChanged(object sender, SelectionChangedEventArgs e)
    {
        if (combo.SelectedIndex == 1) return;

        foreach (var command in Directory.GetFiles(((ThemeData)combo.SelectedValue).ThemePath).Where(x => x.EndsWith(“.xaml”)))
        {
            var stream = new FileStream(command, FileMode.Open);

            foreach (DictionaryEntry dictionaryEntry in (ResourceDictionary)XamlReader.Load(stream))
            {
                Application.Current.Resources[dictionaryEntry.Key] = dictionaryEntry.Value;
            }
        }
    }

This should be pretty straight forward, we get the theme name from combo box, then search the theme folder for xaml files, read&parse and add them to Application Resources. New entries will override the old entries with same Key, so all entries of old theme will be replaced.

 

WindowClipping (21)

 

Best part of this theming thing is that users can easily change whatever they want using just notepad.

All users have to do;

1) Duplicate an existing theme folder;

WindowClipping (22)

 

Optional ) You can name files whatever you want

WindowClipping (23)

3) Edit style as you wish, just keep the style x:Key and TargetType same.

LolThemeFile.xaml - Notepad2 (Administrator)

4) ???

5) Profit

MainWindow (16)

That’s it!

 

I hope you like it or find it useful, next post will be a Towel Theming Sample OR something about WPF Localization. Oh and you can find the sample code of this post below, see you next time!

 

Visual Studio 2010 Sample Solution File

Leave a Reply