Tuesday 19 February 2013

Customize the Windows Phone ToggleButton

Nick Randolph pulls apart the Windows Phone ToggleButton to help you adapt it to the look of your application.
One of the most important aspects of developing a Windows Phone application is understanding and taking advantage of the ability to restyle existing controls. For most applications you'll want to stick with the default style for most controls, as this will help you take advantage of the built-in Windows Phone style. It's usually sufficient to simply replace the content or define item templates in the case of a list. However, there are times when it's necessary to pull apart a control in order to restyle it. Windows Phone makes it easy to restyle a control without losing the basic functionality of the control.
In this article I'll start with the ToggleButton control that comes with the Windows Phone SDK. Figure 1 illustrates the main two states of a ToggleButton that have been dragged from the Assets window in Expression Blend onto the design surface.
Figure 1. ToggleButton states.
In this scenario I'm going to modify the ToggleButton to display a star in two different colors depending on whether it's in the checked (blue) or unchecked (green) state. Figure 2 illustrates the desired output.
Figure 2. Custom ToggleButton states.
The first step in this process is to open Expression Blend, create a new Windows Phone project and add an instance of the ToggleButton to the MainPage. The ToggleButton starts off using the implicit style that comes as part of the platform (as per Figure 1), in which it's possible to add content. For the star I'll use one of the application bar icons that ships with the SDK, which can be found at C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v8.0\Icons\Dark\favs.png (you'll need to add this icon to your project and set the Build Action to Content). The following XAML adds this icon as content to the ToggleButton:
Advertisement

<ToggleButton HorizontalAlignment="Left" VerticalAlignment="Top">
  <Image Source="/favs.png" Stretch="Fill" Height="76" Width="76"/>
</ToggleButton>
This has brought us one step closer to our goal of having different-colored stars. However, if you run your application at this point, you'll notice that when the ToggleButton is in the checked state the background changes color but the star doesn't. In order to change the color of the star, we need to modify the Template for the ToggleButton. To do this, right-click on the ToggleButton in Expression Blend, select Edit Template and then Edit a Copy. Give the new template a name, "ContentToggleButtonStyle," and click OK. This will take you into editting mode for the newly created template. You'll see that the template for the ToggleButton is made up of two distinct parts that determine the layout for when it's enabled and disabled. The first thing I'll do is remove the white box that's part of the default style. Rather than modifying the structure, I'm simply going to remove the BorderBrush, BorderThickness and Margin attributes on both the EnabledBackground and DisabledBackground elements.
Both the EnabledBackground and DisabledBackground have a single ContentControl as their only child. The Image that I added as the Content of the ToggleButton is displayed by these ContentControls. In order to dynamically change the color of the star (without having to specify stars of different colors), I'm going to use the star image as an OpacityMask on a simple Border control. I'll start by replacing both ContentControls with Borders where the OpacityMask has been defined:
<Grid x:Name="EnabledBackground" Background="{TemplateBinding Background}" >
  <Border x:Name="EnabledContent" Background="{TemplateBinding Foreground}" 
    OpacityMask="{TemplateBinding Content}" Height="100" Width="100" />
  <Border x:Name="EnabledCheckedContent" Background="{TemplateBinding BorderBrush}" 
    Opacity="0" OpacityMask="{TemplateBinding Content}" Height="100" Width="100" />
</Grid>
<Border x:Name="DisabledBackground" Background="Transparent" IsHitTestVisible="False" 
  Visibility="Collapsed" Margin="0,-3,0,3">
  <Border x:Name="DisabledContent" Background="{StaticResource PhoneDisabledBrush}" 
    OpacityMask="{TemplateBinding Content}" Height="100" Width="100" />
</Border>
In doing this, you'll notice that the ToggleButton stops rendering the star. This is because it's attempting to use an Image control (which is the current Content of the ToggleButton) for the OpacityMark. This doesn't work, so you need to replace the Image with an ImageBrush, as the following XAML indicates:
<ToggleButton HorizontalAlignment="Left" VerticalAlignment="Top" 
  Style="{StaticResource ContentToggleButtonStyle}">
  <ImageBrush ImageSource="/favs.png" Stretch="UniformToFill"/>
</ToggleButton>
You should now see the star appearing, but if you switch between the visual states while in editing mode for the template for the ToggleButton, you'll notice that Expression Blend displays error messages for certain states. This is because the visual states haven't been updated in line with the changes to the layout of the template. Modify the VisualStateGroups element so it appears like the code in Listing 1.
Listing 1. Modifying the VisualStateGroups element.
<VisualStateManager.VisualStateGroups>
  <VisualStateGroup x:Name="CommonStates">
    <VisualState x:Name="Normal"/>
    <VisualState x:Name="Disabled">
      <Storyboard>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" 
                                       Storyboard.TargetName="EnabledBackground">
          <DiscreteObjectKeyFrame KeyTime="0">
            <DiscreteObjectKeyFrame.Value>
              <Visibility>Collapsed</Visibility>
            </DiscreteObjectKeyFrame.Value>
          </DiscreteObjectKeyFrame>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" 
                                       Storyboard.TargetName="DisabledBackground">
          <DiscreteObjectKeyFrame KeyTime="0">
            <DiscreteObjectKeyFrame.Value>
              <Visibility>Visible</Visibility>
            </DiscreteObjectKeyFrame.Value>
          </DiscreteObjectKeyFrame>
        </ObjectAnimationUsingKeyFrames>
      </Storyboard>
    </VisualState>
  </VisualStateGroup>
  <VisualStateGroup x:Name="CheckStates">
    <VisualState x:Name="Unchecked"/>
    <VisualState x:Name="Checked">
      <Storyboard>
        <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" 
                                             Storyboard.TargetName="EnabledContent" 
                                             d:IsOptimized="True"/>
        <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" 
                                             Storyboard.TargetName="EnabledCheckedContent" 
                                             d:IsOptimized="True"/>
      </Storyboard>
    </VisualState>
  </VisualStateGroup>
  <VisualStateGroup x:Name="FocusStates"/>
</VisualStateManager.VisualStateGroups>
If you look at the new structure, you'll see there are actually two Borders for the enabled state that represent when the ToggleButton is checked and unchecked. These bind to the Foreground and BorderBrush of the ToggleButton control, respectively, allowing you to control the star color by setting the Foreground (unchecked) and BorderBrush (checked) of the ToggleButton itself.
Now, this might seem like a lot of work to go through to simply specify a different-colored star for the different checked state of the ToggleButton. However, what's nice about this implementation is how easily you can reuse this style on a different ToggleButton using a different image and different colors. For example, the following XAML illustrates two instances of the ToggleButton that use different images and different color combinations:
<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  <ToggleButton HorizontalAlignment="Left" VerticalAlignment="Top" 
    Style="{StaticResource ContentToggleButtonStyle}" 
    Foreground="Red" BorderBrush="#FF0CFF00">
    <ImageBrush ImageSource="/favs.png" Stretch="UniformToFill"/>
  </ToggleButton>
  <ToggleButton HorizontalAlignment="Left" VerticalAlignment="Top" 
    Style="{StaticResource ContentToggleButtonStyle}" 
    Foreground="#FFD100FF" BorderBrush="#FF00FFF3">
    <ImageBrush ImageSource="/feature.camera.png" Stretch="UniformToFill"/>
  </ToggleButton>
</StackPanel>
In this article you've seen how to get started modifying a template for one of the built-in controls that ships with the Windows Phone SDK. This is only the tip of the iceberg in terms of what you can do by modifying templates and styles in order to adapt controls to match the style of your application.
http://visualstudiomagazine.com/articles/2013/02/15/customize-windows-phone-togglebutton.aspx

No comments:

Post a Comment