WPF Custom Window Styles

Download Demo Project : CustomSkinDemo.zip

This article shows how to handle multiple skins in WPF at runtime. It also covers how to change the border style of a window.

First of all, you can have a look at the window with different skins (the current skin can be changed with the combo-box embedded inside the title bar):

What you need:

1. A set of skins that shares the same resources. For my example, I’ve started with the WPF Toolkit themes.

2. This example is built with Visual Studio 2010. The code can be downloaded at the end of this article.

So what is important is to set your App.xaml to include a default skin and the custom window style dictionary. In my current implementation, it is important to set that default skin as the first element in the merged dictionaries, because I reserve this slot so that the code that changes the skin at runtime only swaps the content of this slot with another skins.

<Application x:Class="WorkTimerTracker.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:common="clr-namespace:Common"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
 
                <!-- Set default skin -->
                <ResourceDictionary Source="Themes\Skins\ExpressionDark.xaml"/>
                <ResourceDictionary Source="Themes\WindowStyle.xaml"/>
 
            </ResourceDictionary.MergedDictionaries>
 
            <common:SettingConverter x:Key="SettingConv"></common:SettingConverter >
 
        </ResourceDictionary>
    </Application.Resources>
</Application>

Then what is important is to have some code that let the user switch skin at runtime. This part is done in the resource dictionary code behind when the user selects a new element in the combo-box. We use the list element’s tag to hardcode the skin file name. the .xaml part is concatenated dynamically.

private void OnThemeSelectionChanged(object sender,
                                  System.Windows.Controls.SelectionChangedEventArgs e)
{
  string themeXamlFileName = @"";
 
  // Get combo box item tag name
  if ( e.AddedItems.Count &gt; 0 )
  {
    ComboBoxItem selectedItem = (ComboBoxItem)e.AddedItems[0];
    themeXamlFileName = (string)selectedItem.Tag;
    ResourceDictionary skin = new ResourceDictionary();
    skin.Source = new Uri(@"Themes\Skins\" + themeXamlFileName + ".xaml", UriKind.Relative);
 
    Application.Current.Resources.MergedDictionaries[0] = skin;
  }
}

Finally, the custom border style is obtained by implementing a custom style for the window using whatever you want. I’ve choose to stay standard and keep a title bar with a icon and a centered window title, but you can do whatever you want. Here’s the code for the control template that sets the custom style.

<!-- Window Template -->
<ControlTemplate x:Key="WindowTemplate" TargetType="{x:Type Window}">
    <ControlTemplate.Resources>
        <GridLength x:Key="BorderWidth">7</GridLength>
    </ControlTemplate.Resources>
 
    <Grid x:Name="WindowRoot" Style="{DynamicResource WindowRoot}" SizeChanged="OnWindowSizeChanged" Loaded="OnWindowLoaded">
        <Border x:Name="WindowFrame" Style="{DynamicResource WindowBorder}">
            <Grid x:Name="WindowLayout">
 
                <!-- The title bar. -->
                <Grid Margin="0" VerticalAlignment="Top">
                    <Border Style="{DynamicResource TitleBarBackgroundBorder}" 
                            MouseLeftButtonDown="MoveWindow" MouseMove="OnBorderMouseMove" />
                    <Image x:Name="IconApp" Margin="10, 0, 0, 0" HorizontalAlignment="Left" Source="{TemplateBinding Icon}"></Image>
                    <TextBlock Foreground="{DynamicResource TextBrush}" Text="{TemplateBinding Title}" 
                               HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Normal" 
                               MouseLeftButtonDown="MoveWindow"/>
 
                    <!-- Window state buttons -->
                    <StackPanel Margin="0,8" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
                        <ComboBox Margin="0,0,5,0" Height="16" FontSize="8" Padding="3" 
                                  SelectionChanged="OnThemeSelectionChanged"
                                  SelectedIndex="{Binding Path=Settings[SelectedTheme], Mode=TwoWay, TargetNullValue=2, FallbackValue=2}"
                                  >
                            <!-- Load themes dynamically from Themes\Skins -->
                            <ComboBoxItem Tag="None">None</ComboBoxItem>
                            <ComboBoxItem Tag="BureauBlack">Bureau Black</ComboBoxItem>
                            <ComboBoxItem Tag="BureauBlue">Bureau Blue</ComboBoxItem>
                            <ComboBoxItem Tag="ExpressionDark">Expression Dark</ComboBoxItem>
                            <ComboBoxItem Tag="ExpressionLight">Expression Light</ComboBoxItem>
                            <ComboBoxItem Tag="ShinyBlue">Shiny Blue</ComboBoxItem>
                            <ComboBoxItem Tag="ShinyRed">Skiny Red</ComboBoxItem>
                            <ComboBoxItem Tag="WhistlerBlue">Whistler Blue</ComboBoxItem>
                        </ComboBox>
                        <Button Style="{StaticResource MinimizeRadialButton}" Click="MinimizeWindow" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,5,0"/>
                        <Button Style="{StaticResource MaximizeRadialButton}" Click="MaximizeWindow" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,5,0" />
                        <Button Style="{StaticResource CloseRadialButton}" Click="CloseWindow" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,10,0" />
                    </StackPanel>
                </Grid>
 
                <!-- The window content. -->
                <Border x:Name="WindowContent" Margin="0,30,0,0">
                    <AdornerDecorator>
                        <ContentPresenter />
                    </AdornerDecorator>
                </Border>
 
                <!-- Grips -->
                <!-- Sides -->
                <Rectangle x:Name="leftSizeGrip" Style="{StaticResource RectBorderStyle}" Width="7" HorizontalAlignment="Left"/>
                <Rectangle x:Name="rightSizeGrip" Style="{StaticResource RectBorderStyle}" Width="7" HorizontalAlignment="Right"/>
                <Rectangle x:Name="topSizeGrip" Style="{StaticResource RectBorderStyle}" Height="7" VerticalAlignment="Top"/>
                <Rectangle x:Name="bottomSizeGrip" Style="{StaticResource RectBorderStyle}" Height="7" VerticalAlignment="Bottom"/>
                <!--Corners -->
                <Rectangle Name="topLeftSizeGrip" Style="{StaticResource RectBorderStyle}" Width="7" Height="7" HorizontalAlignment="Left" VerticalAlignment="Top"/>
                <Rectangle Name="bottomRightSizeGrip" Style="{StaticResource RectBorderStyle}"  Width="7" Height="7" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
                <Rectangle Name="topRightSizeGrip" Style="{StaticResource RectBorderStyle}"  Width="7" Height="7" HorizontalAlignment="Right" VerticalAlignment="Top"/>
                <Rectangle Name="bottomLeftSizeGrip" Style="{StaticResource RectBorderStyle}"  Width="7" Height="7" HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
            </Grid>
        </Border>
    </Grid>
</ControlTemplate>

The last section defines some invisible rectangles that are used to resize the window. Furthermore, there is some code that defines how the window interacts to user events, such as double clicking on the title bar, dragging the window in Windows 7 to use Aero snapping, etc. This code can be found in WindowStyle.xaml.cs.
To finish, if you want to use all this into your application, I suggest you copy the Themes/ directory completely, or if you wanna be a good software engineer you can make a nice class library with all this and reuse it. If you do, please send it to me :D. Then, when you have copied the Themes/ folder, modify your App.xaml as above and set the Style of your main window as (using : Style=”{DynamicResource SkinWindowStyle}”)

<Window x:Class="WorkTimerTracker.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Style="{DynamicResource SkinWindowStyle}"
        Title="Work Time Tracker" 
        Icon="Resources\App.ico">

Enjoy!

Download Demo Project : CustomSkinDemo.zip

This entry was posted in Coding, WPF and tagged , , , , , . Bookmark the permalink.

9 Responses to WPF Custom Window Styles

  1. Paulo Livramento says:

    Hi,
    this is very useful. Here can i find the source code?

    thanks
    PL

  2. Jonathan says:

    Hi,
    For some unknown reasons, attachment to my article does not work anymore. So I updated the article with a link at the beginning and the end of the post.

    Have a nice day.

  3. Ingrid says:

    Hi Jonathan,
    Very helpful! Thanks a bunch.
    Ingrid

  4. Anne says:

    Thanks a lot.. It made my work easier..

  5. Blazed says:

    Trying to get into WPF after doing everything in winforms for a while, this helped a lot.

  6. HtD says:

    This is great, but resizing is not working for me.

    I see, for example, target.Height changing in WindowResizer as I drag a border, but the window itself does not resize. If I set AllowsTransparency to false I can resize the window normally. Any pointers?

    Thanks

  7. Jonathan says:

    What version of Windows are you using? I has problem with transparency when I was hosting Win32 window in a WPF application, but other than that I didn’t had problem with it.

  8. HtD says:

    It’s OK, Caliburn Micro was defaulting my windows to SizeToContent = SizeToContent.WidthAndHeight, which was unneccessary. Now working beautifully.

    The behaviour of the resizer is interesting on a dual-head XP box btw. I am yet to investigate in detail, but it appears to get confused when dragging resize handles between monitors.

  9. Pankaj says:

    Thanks a lot!

    Application.Current.Resources.MergedDictionaries[0] = skin;

    helped me apply theme to entire application instead of MainWindow only.

Leave a Reply

Your email address will not be published. Required fields are marked *