Friday 25 September 2009

Circular WPF Button Template

Here’s another WPF button template, since my first one continues to be one of the most popular posts on my blog. This one is in response to a question about how the button could be made circular. The basic background of the button is formed by superimposing three circles on top of each other:

<Grid Width="100" Height="100" Margin="5">
   <Ellipse Fill="#FF6DB4EF"/>
   <Ellipse>
      <Ellipse.Fill>
         <RadialGradientBrush>
            <GradientStop Offset="0" Color="#00000000"/>
            <GradientStop Offset="0.88" Color="#00000000"/>
            <GradientStop Offset="1" Color="#80000000"/>
         </RadialGradientBrush>
      </Ellipse.Fill>
   </Ellipse>
   <Ellipse Margin="10">
      <Ellipse.Fill>
         <LinearGradientBrush>
            <GradientStop Offset="0" Color="#50FFFFFF"/>
            <GradientStop Offset="0.5" Color="#00FFFFFF"/>
            <GradientStop Offset="1" Color="#50FFFFFF"/>
         </LinearGradientBrush>
      </Ellipse.Fill>
   </Ellipse>
</Grid>

Here’s what that looks like:

Circular Button

Now to turn it into a template, we follow a very similar process to before. We allow the Background colour to be overriden by the user if required. I couldn’t come up with a neat way of forcing the button to be circular, but it is not too much of a chore to set the Height and Width in XAML.

The focus rectangle has been made circular. For the IsPressed effect, I simply change the angle of the linear gradient of the inner circle a little, and move the ContentPresenter down a bit, which seems to work OK. I haven’t created any triggers yet for IsMouseOver, IsEnabled or IsFocused, mainly because I’m not quite sure what would create a the right visual effect.

<Page.Resources>
  <Style x:Key="MyFocusVisual">
     <Setter Property="Control.Template">
        <Setter.Value>
           <ControlTemplate TargetType="{x:Type Control}">
              <Grid Margin="8">
                 <Ellipse
                    Name="r1"
                    Stroke="Black"
                    StrokeDashArray="2 2"
                    StrokeThickness="1"/>
                 <Border
                    Name="border"
                    Width="{TemplateBinding ActualWidth}"
                    Height="{TemplateBinding ActualHeight}"
                    BorderThickness="1"
                    CornerRadius="2"/>
              </Grid>
           </ControlTemplate>
        </Setter.Value>
     </Setter>
  </Style>
  <Style x:Key="CircleButton" TargetType="Button">
     <Setter Property="OverridesDefaultStyle" Value="True"/>
     <Setter Property="Margin" Value="2"/>
     <Setter Property="FocusVisualStyle" Value="{StaticResource MyFocusVisual}"/>
     <Setter Property="Background" Value="#FF6DB4EF"/>
     <Setter Property="Template">
        <Setter.Value>
           <ControlTemplate TargetType="Button">
              <Grid>
                 <Ellipse Fill="{TemplateBinding Background}"/>
                 <Ellipse>
                    <Ellipse.Fill>
                       <RadialGradientBrush>
                          <GradientStop Offset="0" Color="#00000000"/>
                          <GradientStop Offset="0.88" Color="#00000000"/>
                          <GradientStop Offset="1" Color="#80000000"/>
                       </RadialGradientBrush>
                    </Ellipse.Fill>
                 </Ellipse>
                 <Ellipse Margin="10" x:Name="highlightCircle" >
                    <Ellipse.Fill >
                       <LinearGradientBrush >
                          <GradientStop Offset="0" Color="#50FFFFFF"/>
                          <GradientStop Offset="0.5" Color="#00FFFFFF"/>
                          <GradientStop Offset="1" Color="#50FFFFFF"/>
                       </LinearGradientBrush>
                    </Ellipse.Fill>
                 </Ellipse>
                 <ContentPresenter x:Name="content" HorizontalAlignment="Center" VerticalAlignment="Center"/>
              </Grid>
              <ControlTemplate.Triggers>
                 <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="highlightCircle" Property="Fill">
                       <Setter.Value>
                       <LinearGradientBrush StartPoint="0.3,0" EndPoint="0.7,1">
                          <GradientStop Offset="0" Color="#50FFFFFF"/>
                          <GradientStop Offset="0.5" Color="#00FFFFFF"/>
                          <GradientStop Offset="1" Color="#50FFFFFF"/>
                       </LinearGradientBrush>
                       </Setter.Value>
                    </Setter>
                    <Setter TargetName="content" Property="RenderTransform">
                       <Setter.Value>
                          <TranslateTransform Y="0.5" X="0.5"/>
                       </Setter.Value>
                    </Setter>
                 </Trigger>
              </ControlTemplate.Triggers>
           </ControlTemplate>
        </Setter.Value>
     </Setter>
  </Style>
</Page.Resources>

Here’s how we declare a few of these buttons of different sizes, and with different background colours:

<WrapPanel>
  <Button Width="100" Height="100" Style="{StaticResource CircleButton}">Hello World</Button>
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="#FF9F1014">Button 2</Button>
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="#FFD8C618">Button 3</Button>      
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="#FF499E1E">Button 4</Button>
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="Orange">Button 5</Button>
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="#FF7C7C7C">Button 6</Button>
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="Purple" Foreground="White">Button 7</Button>
  <Button Width="100" Height="100" Style="{StaticResource CircleButton}" Background="#FF3120D4" Foreground="White">Button 8</Button>
</WrapPanel>

Circular Buttons

Sadly, the use of ControlTemplate triggers means that this template can’t be used directly in Silverlight. I’ll maybe look into converting them to VisualStates for a future blog post.

8 comments:

Anonymous said...

Hello Mark,

awesome work!

Please continue to put out this kind of stuff on your blog!

Anonymous said...

Why can't i move the Buttons around? o.O

Kevin Marois said...

How do I copy the code off your blog without getting all the line numbers?

Kevin Marois said...

How do I copy the code off your blog without the line numbers?

Unknown said...

@Kevin - double-click in the code area - it will select the text without the line numbers.

Anonymous said...

how to wrap the button text

Anonymous said...

Hi, I am getting this message:
The attached property 'Page.Resources' is not defined on or one of its base classes.

Can you help me, please?

Phil Ruse said...

Having switched to WPF end of last year, on the one hand it's scary how much I have to learn (though that's what makes it fun too), on the other I am continually grateful for contributions to 'the community' (if that doesn't sound too pompous?!) such as this. Much obliged :)