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.

Post a Comment