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 type | Style Key |
Button | StandardButton |
Button | LinkButton |
TabControl | StandardTabControl |
Menu | StandardMenu |
ListBox | StandardListBox |
ScrollBar | StandardScrollBar |
TextBox | StandardTextBox |
TextBox | SearchTextBox |
DataGrid | StandardDataGrid |
ComboBox | StandardComboBox |
GroupBox | StandardGroupBox |
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 neededSnapsToDevicePixels
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 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 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 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>
-->
</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