Introduction
I really liked the look of the round glassy button style in Windows Vista ever since I installed it. The great thing about WPF is that it allows styling virtually any user interface component. After playing a while with Microsoft Expression Blend, I came up with a style that, in my opinion, looks pretty much like the style I was after. So, I thought I might share it. As I said, I've used Microsoft Expression Blend to draw the button, I've also used the great XAML editor - Kaxaml - for some fine tuning.
Overview
The glass button style consists of three layers, which form the glass effect, and a ContentPresenter
to hold the content of the button. All the layers are laid out in a grid, on top of each other. There are also triggers defined, for mouse over and pressed states of the button, to add some interactivity.
I've defined a keyed window resource with the style, but the key can be removed to apply the style to all the buttons in the window.
OK, let's go through each of the layers to see how this cool glass look, widely used in Microsoft products, is achieved.
Button Layers
Background Layer
The first layer is an ellipse which is actually the canvas for drawing the reflection and the refraction layers. Nothing fancy, the fill color of the ellipse is wired to the Background
property of the button.
Here is the screenshot of the Blend workspace with a dark blue ellipse:
<Ellipse Fill="{TemplateBinding Background}"/>
Refraction Layer
The second layer imitates a refraction of light falling from top to bottom of the button. The reason this layer is being drawn before the reflection layer is that the reflection layer must have a hard edge somewhere in the middle of the button ellipse, to get the shiny glass look. This layer is actually another ellipse, but this time, we will use a radial white-to-transparent gradient fill, to imitate the light refraction. The gradient starts from the bottom middle, and goes to the top middle of the ellipse; however, to decrease the intensity of the refraction highlight, the gradient starts way below the bottom edge of the ellipse. This can be clearly seen on the screenshot and the XAML code below:
<Ellipse x:Name="RefractionLayer">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.496,1.052">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5"
CenterY="0.5" ScaleX="1.5" ScaleY="1.5"/>
<TranslateTransform X="0.02" Y="0.3"/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Offset="1" Color="#00000000"/>
<GradientStop Offset="0.4" Color="#FFFFFFFF"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
Reflection Layer
The third layer is the light reflection layer, which is, actually, the hardest part of the effect. The problem is that the reflection cannot be drawn using any of the standard shapes. So, I decided to use a Path to draw the region of the reflection. Of course, it is possible to draw the path manually, but, frankly, this is not something somebody may enjoy (unless you are an artist and have a nice digitizer). Anyway, I drew yet another ellipse in MS Blend and converted it to a Path, then I just played around with Path Bezier points to get the final smooth path. The good thing about the Path is that you can apply the gradients to a complex Path
object, exactly like you would do for any other predefined shape primitives like Ellipse or Rectangle. So, to get the shiny reflection, we will need a transparent-to-white radial gradient fill, starting from the bottom of the path (somewhere in the middle of the button) up to the top edge. I guess if I was an artist, I would be able to formalize the gradient stop for the radial gradient. But, I'm not an artist, so I kept playing with the settings in Blend, until I got the look I was happy with. And, since we have our button laid out in a grid, we should set VerticalAlignment="Top"
, so the reflection region doesn't end up in the center of the button.
<Path x:Name="ReflectionLayer" VerticalAlignment="Top" Stretch="Fill">
<Path.RenderTransform>
<ScaleTransform ScaleY="0.5" />
</Path.RenderTransform>
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="True" StartPoint="98.999,45.499">
<BezierSegment Point1="98.999,54.170" Point2="89.046,52.258"
Point3="85.502,51.029"/>
<BezierSegment IsSmoothJoin="True" Point1="75.860,47.685"
Point2="69.111,45.196" Point3="50.167,45.196"/>
<BezierSegment Point1="30.805,45.196" Point2="20.173,47.741"
Point3="10.665,51.363"/>
<BezierSegment IsSmoothJoin="True" Point1="7.469,52.580"
Point2="1.000,53.252" Point3="1.000,44.999"/>
<BezierSegment Point1="1.000,39.510" Point2="0.884,39.227"
Point3="2.519,34.286"/>
<BezierSegment IsSmoothJoin="True" Point1="9.106,14.370"
Point2="27.875,0" Point3="50,0"/>
<BezierSegment Point1="72.198,0" Point2="91.018,14.466"
Point3="97.546,34.485"/>
<BezierSegment IsSmoothJoin="True" Point1="99.139,39.369"
Point2="98.999,40.084" Point3="98.999,45.499"/>
</PathFigure>
</PathGeometry>
</Path.Data>
<Path.Fill>
<RadialGradientBrush GradientOrigin="0.498,0.526">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5"
CenterY="0.5" ScaleX="1" ScaleY="1.997"/>
<TranslateTransform X="0" Y="0.5"/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Offset="1" Color="#FFFFFFFF"/>
<GradientStop Offset="0.85" Color="#92FFFFFF"/>
<GradientStop Offset="0" Color="#00000000"/>
</RadialGradientBrush>
</Path.Fill>
</Path>
Finally, I added a ContentPresenter
to the center of the button. Some experiments show that moving the content area down for 1 pixel gives a more nice looking button, so a margin is also applied to the ContentPresenter
(remember that, since the content area is centered in the grid, the top margin of 2px is actually moving it down by a pixel).
<ContentPresenter Margin="0,2,0,0"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
OK, here is the final look of the glass button in the Blend workspace:
Adding Some Interactivity
Now, let's add some interactivity to our button.
Mouse Hover Effect
To get a mouse over effect, we will need to increase the intensity of the light source. To do so, let's define a trigger for the IsMouseOver
event, copy-paste the gradient setting for both the reflection and refraction layers, and fine tune the gradient alpha values and offsets to highlight the button. For the refraction layer, I just moved the source of the radial gradient a little bit higher, whereas for the reflection layer, I changed the gradient stops to be less transparent white.
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="RefractionLayer" Property="Fill">
<Setter.Value>
<RadialGradientBrush GradientOrigin="0.496,1.052">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5"
ScaleX="1.5" ScaleY="1.5"/>
<TranslateTransform X="0.02" Y="0.3"/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Offset="1" Color="#00000000"/>
<GradientStop Offset="0.45" Color="#FFFFFFFF"/>
</RadialGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="ReflectionLayer" Property="Fill">
<Setter.Value>
<RadialGradientBrush GradientOrigin="0.498,0.526">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5"
ScaleX="1" ScaleY="1.997"/>
<TranslateTransform X="0" Y="0.5"/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Offset="1" Color="#FFFFFFFF"/>
<GradientStop Offset="0.85" Color="#BBFFFFFF"/>
<GradientStop Offset="0" Color="#00000000"/>
</RadialGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
Mouse Pressed Effect
For the IsPressed
event, we will need to decrease the highlights. So, we need to move the reflection layer light source (radial gradient start point) a little bit down, and for the reflection layer, we need to make the gradient stops more transparent, to dim the light falling on our button.
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="RefractionLayer" Property="Fill">
<Setter.Value>
<RadialGradientBrush GradientOrigin="0.496,1.052">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5"
ScaleX="1.5" ScaleY="1.5"/>
<TranslateTransform X="0.02" Y="0.3"/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Offset="1" Color="#00000000"/>
<GradientStop Offset="0.3" Color="#FFFFFFFF"/>
</RadialGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="ReflectionLayer" Property="Fill">
<Setter.Value>
<RadialGradientBrush GradientOrigin="0.498,0.526">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5"
ScaleX="1" ScaleY="1.997"/>
<TranslateTransform X="0" Y="0.5"/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Offset="1" Color="#CCFFFFFF"/>
<GradientStop Offset="0.85" Color="#66FFFFFF"/>
<GradientStop Offset="0" Color="#00000000"/>
</RadialGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
Again, the values for the gradient stops have been chosen experimentally, I cannot give any exact values for getting the glassy look.
Using the Code
To use the style, just merge the style resource defined in the GlassButton.xaml file with the resource of your Window or Page and set the style of the button to {StaticResource GlassButton}
. To set the color of the button, use the button's Background
property:
<Window x:Class="GlassButton.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Glass Buttons" Height="228" Width="272">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources\GlassButton.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Button Style="{StaticResource GlassButton}" Width="50"
Height="50" Background="#FF660707" Margin="10"/>
</Grid>
</Window>
Here is a demo (you can find the source code attached to the article) with some buttons with icons on a nice background (borrowed from the Windows Vista wallpapers):
History
- 05 January, 2009: Initial version