Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Visual Studio 2012 Metro Styles for WPF

0.00/5 (No votes)
9 Dec 2012 2  
Black Metro Styles for Button, ListBox, Menu, ScrollBar, TabControl, TextBox, ComboBox, DataGrid and GroupBox

Contents

Introduction

Although there are still serious discussions about whether the "Metro" design concept from Microsoft is good or not, WPF developers, who follow the trend, may now write their styles keeping the concept in mind. But despite the fact that many people think about the Tile Wall of Windows 8, when they hear the word "Metro", the concept itself actually means much more than that. The main idea is to keep things simple. No more gradients, no more colorful, blinking, glassy, flashing surfaces. One of the applications demonstrating this concept best is, no doubt, the new Visual Studio 2012. This article will present some Visual Studio like WPF styles.

"Styles like visual studio" means, that I didn´t copy the styles of course (using a decompiler or something) and so they aren´t actually equal, but I used the same colors and I structured my demo application like VS. I will discuss some of the main differences later.

Screenshots

First some screenshots of what we´ll end up with, to show you what I´m talking about:

Using the styles

To structure the styles a bit, I´ve put each in a different ResourceDictionary named after the control to be styled. If you don´t want to use all styles, you´ll have to embed each ResourceDictionary by yourself via MergedDictionaries. In this case it´s also necessary to reference the styles explicitly, because they all have a key. If you want to use the complete set, just add the Styles.xaml ResourceDictionary to the MergedDictionaries.

Use complete style set

App.xaml:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary 
                Source="pack://application:,,,/Selen.Wpf.SystemStyles;component/Styles.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

styles will be automatically applied to each control

Use parts of it

App.xaml:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary 
                Source="pack://application:,,,/Selen.Wpf.SystemStyles;component/ButtonStyles.xaml"/>
            <ResourceDictionary 
                Source="pack://application:,,,/Selen.Wpf.SystemStyles;component/TextBoxStyles.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

where you want to use the style:

<Button Content="blastallenemies.cmd" Style="{StaticResource LinkButton}"/>

List of all styles

For future reference, here is a complete list of the styles and the corresponding keys:

Target typeStyle Key
ButtonStandardButton
ButtonLinkButton
TabControlStandardTabControl
MenuStandardMenu
ListBoxStandardListBox
ScrollBarStandardScrollBar
TextBoxStandardTextBox
TextBoxSearchTextBox
DataGridStandardDataGrid
ComboBoxStandardComboBox
GroupBoxStandardGroupBox

Concept

Some words about metro

Before we´ll talk about the WPF implementation, I want to summarize the general goal we are pursuing:

  • In short, Metro means focusing on the content
  • Only sharp edges
  • No blurry lines
  • No gradients
  • Only few colors (this is not always the case, but I like it, because it again underlines the importance of the content)

As I focused on the black style, we can add the following two rules:

  • Dark background
  • White foreground

What does this mean?

Now we know what we want, so let´s see what this means in XAML:

Colors

As we only have few colors, I decided to put them all in a seperate ResourceDictionary (Resources.xaml in Selen.Wpf.Core) for easy access. We basically have:

  • Window Background (I applied that by a separate window style, wich I didn´t consider worth being mentioned before, because it does nothing more than that)
  • Foreground for everything
  • Normal, Highlighted and Selected Background and BorderBrush (for many controls the same)

Borders

Borders can be found in (nearly) every style. Because they all look similar, we can say something general about them:

  • BorderThickness should be equal to 1 or 0 (large borders are not proper for the metro style)
  • CornerRadius should be always 0 (no rounded edges)
  • Background and BorderBrush should be set to the mentioned colors as needed
  • SnapsToDevicePixels should be set to true. For those of you, who don´t know about this property: it basically tells WPF to turn off antialiasing and so make a hard cut between the Border and its environment. This prevents our Border from appearing blurry.

Note: There are some exceptions to these rules, because I sometimes use a Border to do other things than creating an actual border, as we´ll see later.

Example

The best example so far is the Button Style, because it just consist of a Border. So here it is, nothing to be surprised about:

<Style x:Key="StandardButton" TargetType="Button">
    <Setter Property="Visibility" Value="Visible"/>
    <Setter Property="Foreground" Value="{StaticResource Foreground}"/>
    <Setter Property="Background" Value="{StaticResource BackgroundNormal}"/>
    <Setter Property="BorderBrush" Value="{StaticResource BorderBrushNormal}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border SnapsToDevicePixels="True"
                        BorderThickness="1"
                        BorderBrush="{TemplateBinding BorderBrush}" 
                        Background="{TemplateBinding Background}">
                    <Grid>
                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="BorderBrush" Value="{StaticResource BorderBrushHighlighted}" />
                        <Setter Property="Background" Value="{StaticResource BackgroundHighlighted}" />
                    </Trigger>
                    <Trigger Property="IsPressed" Value="True">
                        <Setter Property="Background" Value="{StaticResource BackgroundSelected}"/>
                        <Setter Property="BorderBrush" Value="{StaticResource BorderBrushSelected}"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Visibility" Value="Hidden"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Implementation

I don´t want to discuss every single line of the styles now, because many things are the same like what we already discussed or are just logical and don´t really need much explanation to be understood (for instance switching background via Trigger on MouseEnter). But I want to point some interesting things out, on the one hand to give you some ideas for your own work, on the other hand to give you a better understanding of what I did.

Link Button

First the two "out of band" styles - LinkButton and SearchTextBox. The idea of the LinkButton is pretty simple, it´s just a TextBlock that wrapps up the ContentPresenter:

<ControlTemplate TargetType="Button">
    <TextBlock><ContentPresenter/></TextBlock>
</ControlTemplate>

The Button forwards its Foreground to the child elements, so we can change the Foreground of our TextBlock that way in an ordinary Trigger:

<Style.Triggers>
    <Trigger Property="IsMouseOver" Value="true">
        <Setter Property="Foreground" Value="{StaticResource LinkButtonHighlightedForeground}" />
    </Trigger>
</Style.Triggers>

Search TextBox

The SearchTextBox is basically a normal TextBox with a special ControlTemplate that hosts the Text itself and an additional TextBlock, wich is responsible for displaying the "Search ..." text. Our second task is to expand the functionality of our Triggers to hide/display the "Search ..." text besides the background color switching:

<ControlTemplate TargetType="{x:Type TextBox}">
    <Grid Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
        <TextBlock Foreground="{StaticResource SearchTextForeground}" Margin="5,0,0,0" 
                   VerticalAlignment="Center" Name="search" Text="Search ..." Visibility="Hidden"/>
        <ScrollViewer x:Name="PART_ContentHost" Margin="1"/>
    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="TextBox.Text" Value="">
            <Setter TargetName="search" Property="Visibility" Value="Visible"/>
        </Trigger>
        <Trigger Property="TextBox.Text" Value="{x:Null}">
            <Setter TargetName="search" Property="Visibility" Value="Visible"/>
        </Trigger>
        <Trigger Property="IsMouseOver" Value="true">
            <Setter Property="Background" Value="{StaticResource TextBoxBackgroundSelected}" />
            <Setter TargetName="search" Property="Foreground" Value="{StaticResource Foreground}" />
        </Trigger>
        <Trigger Property="IsFocused" Value="true">
            <Setter Property="Background" Value="{StaticResource TextBoxBackgroundSelected}" />
            <Setter TargetName="search" Property="Visibility" Value="Hidden"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Tab Control

The TabControl ControlTemplate might be worth a word or two. That´s because mostly just the TabItems are styled, so you might be interested in something different. There are two main reasons for the ControlTemplate to be changed:

  • Adding the blue horizontal line between the header and the content part of the TabControl (we´re "misusing" a Border for this task)
  • Changing the background of the header to transparent (different than the TabControl´s background itself). That´s because we want to seperate our tab content from the environment by giving it another background.

As you can see, the major changes in comparision to the default template are two Borders, one for the horizontal line and one for the tab content´s background.

<ControlTemplate TargetType="{x:Type TabControl}">
    <Grid KeyboardNavigation.TabNavigation="Local">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Border Background="Transparent" BorderThickness="0,0,0,3" 
                BorderBrush="{StaticResource BackgroundSelected}">
            <TabPanel Name="HeaderPanel" Panel.ZIndex="1" Margin="0,0,4,-1" 
                IsItemsHost="True" KeyboardNavigation.TabIndex="1"/>
        </Border>
        <Border Grid.Row="1" Background="{StaticResource Background}"/>
        <ContentPresenter Grid.Row="1" Name="PART_SelectedContentHost" 
                          ContentSource="SelectedContent"/>
    </Grid>
</ControlTemplate>

The TabItem Style is again very straightforward.

Adding a border around an expanded MenuItem

This is something strange now, and it´s a very small thing, that perhaps nobody will ever take notice of, but I saw it in VS and delighted in it: it´s the border around an expanded MenuItem.

As you may ask yourself, what´s strange about such a thing, let´s first have a look at this picture (the border I mean is highlighted red):

The problem is obvious: We need such a custom shaped border. But as we actually have a MenuItem and a Popup among it, we are forced to create two Borders. This might give you an idea and .. yes, it´s that dirty. We´re creating a third Border, wich overlays the lower border partially, where the two first borders converge. So we´re binding the Width of the third border to the ActualWidth of the first one. Doing this while having SnapsToDevicePixels set to true, leads to some very weird effects, because WPF sometimes "snaps" the MenuItem to a different physical pixel than our third border, so we have to make an exception to this rule. Here is the ready ControlTemplate:

<ControlTemplate TargetType="{x:Type MenuItem}">
    <!--Border 1-->
    <Border x:Name="Border" Background="Transparent" BorderBrush="Transparent" 
            BorderThickness="1" SnapsToDevicePixels="False">
        <Grid x:Name="Grid">
            <Grid.ColumnDefinitions>
                <ColumnDefinition x:Name="Col0" MinWidth="17" Width="Auto" 
                                  SharedSizeGroup="MenuItemIconColumnGroup"/>
                <ColumnDefinition Width="Auto" SharedSizeGroup="MenuTextColumnGroup"/>
                <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIGTColumnGroup"/>
                <ColumnDefinition x:Name="Col3" Width="14"/>
            </Grid.ColumnDefinitions>
            <ContentPresenter Grid.Column="0" x:Name="Icon" VerticalAlignment="Center" 
                              ContentSource="Icon"/>
            <ContentPresenter Grid.Column="1" Margin="{TemplateBinding Padding}" 
                              x:Name="HeaderHost" RecognizesAccessKey="True" 
                              ContentSource="Header" VerticalAlignment="Center"/>
            <ContentPresenter Grid.Column="2" Margin="8,1,8,1" x:Name="IGTHost" 
                              ContentSource="InputGestureText" VerticalAlignment="Center"/>
            <Grid Grid.Column="3" Margin="4,0,6,0" x:Name="ArrowPanel" VerticalAlignment="Center">
                <Path x:Name="ArrowPanelPath" HorizontalAlignment="Right" VerticalAlignment="Center" 
                      Fill="{TemplateBinding Foreground}" Data="M0,0 L0,8 L4,4 z"/>
            </Grid>
            <Popup IsOpen="{Binding Path=IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" 
                   Placement="Right" HorizontalOffset="-1" x:Name="SubMenuPopup" Focusable="false"
                   PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}"
                   AllowsTransparency="True">
                <Grid Margin="0,0,5,5">
                    <!--Border 2-->
                    <Border x:Name="SubMenuBorder" 
                            BorderBrush="{StaticResource MenuSeparatorBorderBrush}"
                            BorderThickness="1" Background="{StaticResource SubmenuItemBackground}" 
                            SnapsToDevicePixels="True">
                        <Grid x:Name="SubMenu" Grid.IsSharedSizeScope="True" Margin="2">
                            <StackPanel IsItemsHost="True" 
                                        KeyboardNavigation.DirectionalNavigation="Cycle"/>
                        </Grid>
                        <Border.Effect>
                            <DropShadowEffect ShadowDepth="2" Color="Black"/>
                        </Border.Effect>
                    </Border>
                    <!--Border 3-->
                    <Border Margin="1,0,0,0" x:Name="TransitionBorder" Width="0" Height="2" 
                            VerticalAlignment="Top" HorizontalAlignment="Left" 
                            Background="{StaticResource SubmenuItemBackground}" SnapsToDevicePixels="False"
                            BorderThickness="1" BorderBrush="{StaticResource SubmenuItemBackground}"/>
                </Grid>
            </Popup>
        </Grid>
    </Border>
    <ControlTemplate.Triggers>
    <!--A whole bunch of triggers-->
</ControlTemplate>

As the Triggers only do the usual work (changing background, and so on) and there are quite many of them, I decided to leave them out. If you´re interested, download the source and have a look at them.

ScrollBar

Because VS still has the (in my opinion inappropriate) standard ScrollBar Style, I decided to build an own. The idea is simple. To fit into the style set, we need a rectangular ScrollBarThumb and rectangular ScrollBarLineButtons. The implementation is then again very straightforward: A border and the corresponding triggers. If we take the ScrollBarThumb, for instance, it looks exactly like the Button Style we talked about before:

<Style x:Key="ScrollBarThumb" TargetType="{x:Type Thumb}">
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="IsTabStop" Value="false"/>
    <Setter Property="Focusable" Value="false"/>
    <Setter Property="Background" Value="{StaticResource BackgroundNormal}"/>
    <Setter Property="BorderBrush" Value="{StaticResource BorderBrushNormal}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Thumb}">
                <Border Background="{TemplateBinding Background}" 
                        BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" />
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Background" Value="{StaticResource BackgroundHighlighted}"/>
                        <Setter Property="BorderBrush" Value="{StaticResource BorderBrushHighlighted}"/>
                    </Trigger>
                    <Trigger Property="IsDragging" Value="True">
                        <Setter Property="Background" Value="{StaticResource BackgroundSelected}"/>
                        <Setter Property="BorderBrush" Value="{StaticResource BorderBrushSelected}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The demo application

For demonstration purposes, I´ve put together a little application. Don´t expect it to do something senseful, it´s just for showing the styles and there´s absolutely no logic behind it. Even the close buttons of the TabControl don´t work, because I dind´t want to have any single line C# code in the project to keep you from having to winkle the bindings to code out the styles and so to make it as easy as possible for you to use the styles in your own applications.

MetroWindow Update

The demo now makes use of the MetroWindow from the MahApps.Metro project (You can find the dll in the lib directory). To make the MetroWindow looking like Visual Studio, we have to add the following to Resources.xaml in Selen.Wpf.Core:

<Color x:Key="AccentColor">#2D2D30</Color>
<Color x:Key="WhiteColor">#2D2D30</Color>
<Color x:Key="BlackColor">#FFFFFFFF</Color>

AccentColor defines the background of the Titlebar (where the close, minimize .. buttons are placed on) and WhiteColor is the MetroWindow background, wich is the same like the first one to get the VS look. BlackColor sets the Foreground for everything.

Note that you can´t use most of the other MahApps.Metro controls with this AccentColor, because they also depend on it. There is unfortunately no option to set just the background of the TitleBar, but as we only use the MetroWindow in this example, it´s possible to do it via the AccentColor.

Points of interest

I think many of you know this, but in my opinion it´s crucial to set FocusVisualStyle to null on any TabItem or ListBoxItem to keep them from getting these ugly dashed borders around them when being selected by keyboard, so I just wanted to mention it, never forget that!

<Setter Property="FocusVisualStyle" Value="{x:Null}"/>

Conclusion

Ok, as you kept up till here, I think you don´t totally dislike the Metro concept (at least if you didn´t skip the whole article), so I hope you liked my styles as well. Of course, design is a controversial subject, but having a design concept in mind brings some structure into your application and I think an application with each control styled the same way, nevertheless you might call this boring, is more user friendly than an application with another cool, flashy effect for each control.

History

  • 9th Dec 2012 - Closing logic for TabItems, TreeView style
  • 10th Oct 2012 - Added style for GroupBox, fixed horizontal ScrollBar issue
  • 03rd Oct 2012 - Added style for ComboBox
  • 30th Sept 2012 - Added style for DataGrid
  • 28th Aug 2012 - Demo uses MahApps.Metro MetroWindow
  • 22nd Aug 2012 - first publish

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here