Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Creating the slider toggle control for Windows Phone 7

DZone's Guide to

Creating the slider toggle control for Windows Phone 7

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

Today Windows Phone 7 was officially released on the US market and it seems like a lot of people are enjoying it. One unique part about the operating system this device runs on is the UI - Metro. Despite the fact that most of the controls are bundled by default with the SDK, you can't find the ToggleSwitch to be there. Of course, you can download the Silverlight Toolkit for Windows Phone 7 where this control is included, but today I am going to show you how to create a basic control with similar functionality on your own - I started this project just for fun - wanted to see how complicated it will be, and it turned out to be pretty interesting, and now I can share the way I did it.

First of all, I must say that I am working with a custom control in the context of an existing Windows Phone 7 application. So if you don't have one at hand, simply create a new Windows Phone 7 Application:

Now you should have the basic structure ready. In your project, add a new class and name it MyControl.cs. Of course, you can change the name if you want to, but if that's the case, make sure you use the correct references across the application.

Once the class "skeleton" is ready, make sure to set the base class for MyControl to Button - I am specifically using Button to tie the user input to the Click event handler, that is present in the Button class.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace WindowsPhoneApplication1
{
public class MyControl:Button
{

}
}

Now it's time to define the actual look for the control, and it will be nothing like the regular button. Silverlight (remember that's what we're using for this application) has some interesting requirements when it comes to styling. When a custom control is used, Silverlight will automatically look for the Generic.xaml file that is located in the Themes folder in the context of the assembly where the control is defined. This applies to custom controls derived from the Control class (hence the Button class, which derives from Control). Since I already defined the control class in the main assembly, I created a Themes folder and created a blank Generic.xaml file inside the folder:

Now let's actually define the style. In Generic.xaml, I am using a ResourceDictionary that contains a control style that defines a ControlTemplate specific to MyControl.

Notice that I am using the MC namespace, which is referencing the application assembly, where MyControl is located.

<ResourceDictionary 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:MC="clr-namespace:WindowsPhoneApplication1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Style TargetType="MC:MyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="MC:MyControl">
<Grid x:Name="mainGrid" Height="50" Width="130">
<Grid.Resources>

<Storyboard x:Name="mainAnimator">
<DoubleAnimation Duration="0:0:0.2" To="7.375" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="rectangle" d:IsOptimized="True"/>
<DoubleAnimation Duration="0:0:0.2" To="51" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="rectangle" d:IsOptimized="True"/>
<DoubleAnimation Duration="0:0:0.2" To="96" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="Scroller" d:IsOptimized="True"/>
</Storyboard>
<Storyboard x:Name="reverseAnimator">
<DoubleAnimation Duration="0:0:0.2" From="7.375" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="rectangle" d:IsOptimized="True"/>
<DoubleAnimation Duration="0:0:0.2" From="51" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="rectangle" d:IsOptimized="True"/>
<DoubleAnimation Duration="0:0:0.2" From="96" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="Scroller" d:IsOptimized="True"/>
</Storyboard>
</Grid.Resources>

<Rectangle Stroke="{StaticResource PhoneForegroundBrush}" Fill="Transparent" StrokeThickness="1"></Rectangle>
<Rectangle x:Name="rectangle" Fill="{StaticResource PhoneAccentBrush}" Margin="5,5,0,5" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Left" Width="16">
<Rectangle.RenderTransform>
<CompositeTransform/>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="Scroller" Fill="{StaticResource PhoneForegroundBrush}" Width="35" HorizontalAlignment="Left" RenderTransformOrigin="0.5,0.5">
<Rectangle.RenderTransform>
<CompositeTransform/>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

The two Storyboard elements that I have here - mainAnimator and reverseAnimator - are used to move the slider across the main grid. Since mainAnimator is moving to the right, I am expanding the colored rectangle and at the same time move the slider. Therefore, I get the filling effect (similar to a progress bar). reverseAnimator will do this backwards, going to the left and emptying the grid (minimizing the colored rectangle).

I have pre-defined height and width parameters set for testing purposes. You are more than welcome to adjust these to your personal needs.

Pay close attention to one detail here - I am not directly referencing any brushes by their direct color name, but rather getting the ones defined as static resources on the phone itself (e.g. PhoneForegroundBrush) - this is generally a good practice to follow when you are developing for Windows Phone because that way you are ensuring control usability when the user decides to adapt the phone to his own needs (e.g. switch between dark and light themes or change the accent color).

Now I am going to work a bit more on the code-behind. First of all, I am going to add a property that will define the current state of the control:

public bool IsOn { get; set; }

When the property is set to true, the slider is all the way on the right side of the container grid and the filling rectangle is extended all the way. The opposite applies when the property is set to false.

So now, I am going to work on the control class constructor:

public MyControl()
{
IsOn = false;
this.DefaultStyleKey = typeof(MyControl);
this.Click += new RoutedEventHandler(MyControl_Click);
}

I am setting IsOn to false, since the default state of the control will be defined by the slider being all the way on the left side of the container grid. DefaultStyleKey will actually get the style and apply the template defined in Generic.xaml.

Then, there is the Click event handler that I was talking about a little bit earlier - it will handle the control touch.

Before showing you the event handler, add a couple more objects to the class itself:

Grid grid;
ResourceDictionary resourceDict;
Storyboard sBoard;

Now let's take a look at how this event handler is implemented:

void MyControl_Click(object sender, RoutedEventArgs e)
{
if (grid == null)
{
grid = (Grid)VisualTreeHelper.GetChild((Button)sender, 0);
resourceDict = grid.Resources;
}

if (!IsOn)
{
sBoard = resourceDict["mainAnimator"] as Storyboard;
sBoard.Begin();
IsOn = true;
}
else
{
sBoard = resourceDict["reverseAnimator"] as Storyboard;
sBoard.Begin();
IsOn = false;
}
}

This code is tied to the control template. If the Grid instance defined in the class is null, this means that the control was just initialized, therefore I am using VisualTreeHelper to get the first (index zero) child element inside the button template, and this is the main grid.

What particularly interests me about that grid is the ResourceDictionary that contains the Storyboard elements, therefore I am setting my ResourceDictionary instance to the one defined in the Grid.

This all happens once the user clicks the control and only if this is the  first time the control is used. If it is not, then I am checking the state (On/Off) and triggering the proper Storyboard animation. Also, don't forget to change the IsOn property to the opposite of the current value in order for the animation to happen on multiple clicks (going back and forth between states). 

Let's look at the results:

Now that's pretty cool! And of course, you can access the IsOn property externally. In the next article I will be talking about implementing DependencyProperty in order to be able to set the IsOn property externally and at the same time modify the state of the control.

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}