In this lesson I will create a custom user control for a
Tic Tac Toe game for Windows Phone 7. Control will be created using several path elements, visual states and a canvas element as a container. Layout for control is created using a Beta edition of
Expression Blend 4. In my next lesson I will create a game-board and implement functionality for it.
Requirements:
- Visual Studio 2010 with Windows Phone Developer Tools
- Windows Phone 7 emulator
- Silverlight 4+
Source code:
- Latest version of Tic Tac Toe game is located on CodePlex website. There you can find a source code for a custom user control developed in this tutorial.
One important thing that I've forgot to mention in a video is how to add user control to you XAML manually. To do so you simply have to declare a namespace within your XAML:
xmlns:local="clr-namespace:WindowsPhoneTicTacToe"
and then you can add your user control inside of your XAML like that:
<local:TicTacToeButton HorizontalAlignment="Left" VerticalAlignment="Top" Height="60" Width="60" />
In my tutorial namespace reference is added automatically and user control is added simply by using drag-and-drop.
Screencast on Vimeo (video was recorded while I've had Visual Studio for Windows Phone CTP version intalled):
HD Windows Phone 7 Custom Control for Tic Tac Toe by EugeneDotnet from Eugene Dotnet on Vimeo.
TicTacToeButton XAML will look like that:
<UserControl x:Class="WindowsPhoneTicTacToe.TicTacToeButton"
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="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignWidth="60" d:DesignHeight="60" Cursor="Hand">
<Grid x:Name="LayoutRoot" Width="60" Height="60" Opacity="1" Background="Transparent">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="TicTacToeButtonStates">
<VisualState x:Name="Default"/>
<VisualState x:Name="Cross">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="path" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="path1" d:IsOptimized="True"/>
<ColorAnimation Duration="0" To="White" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)" Storyboard.TargetName="path1" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="2" Storyboard.TargetProperty="(Shape.StrokeThickness)" Storyboard.TargetName="path1" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="1.024" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="path1" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="0.5" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="path1" d:IsOptimized="True"/>
<ColorAnimation Duration="0" To="White" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)" Storyboard.TargetName="path" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="2" Storyboard.TargetProperty="(Shape.StrokeThickness)" Storyboard.TargetName="path" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="1.048" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="path" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="path" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Nought">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="path2" d:IsOptimized="True"/>
<ColorAnimation Duration="0" To="White" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)" Storyboard.TargetName="path2" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="2" Storyboard.TargetProperty="(Shape.StrokeThickness)" Storyboard.TargetName="path2" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="1.084" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="path2" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="-3.083" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="path2" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Path x:Name="path" Data="M-1.5894572E-07,-3.5527137E-15 C4.1656914,4.3908639 15.52839,10.918503 17.333334,16.333334 C22.318741,17.330414 26.620918,23.479309 29.333334,28 C31.796015,32.104469 36.741779,36.356804 40.333332,39.666668" Fill="{x:Null}" Margin="7.333,12,11.333,7.333" Opacity="0" StrokeStartLineCap="Flat" StrokeEndLineCap="Flat" Stroke="Black" StrokeThickness="1" StrokeMiterLimit="10" StrokeLineJoin="Miter" UseLayoutRounding="False" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<CompositeTransform/>
</Path.RenderTransform>
</Path>
<Path x:Name="path1" Data="M40.666668,-3.5527137E-15 C35.157913,3.7949185 27.844921,7.0956602 23.666666,12.666667 C20.548512,16.824207 16.592587,20.476198 13.333333,24.666666 C8.6571426,30.678911 4.3249264,36.545231 0,42.666668" Fill="{x:Null}" Margin="6,12,12.333,4.333" Opacity="0" StrokeStartLineCap="Flat" StrokeEndLineCap="Flat" Stroke="Black" StrokeThickness="1" StrokeMiterLimit="10" StrokeLineJoin="Miter" UseLayoutRounding="False" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<CompositeTransform/>
</Path.RenderTransform>
</Path>
<Path x:Name="path2" Data="M21.386688,0.66666698 C18.104256,0.75869775 17.090931,-0.16339432 14.386687,2.0000002 C12.42321,3.5707819 10.433279,3.7392514 8.7200203,5.666667 C6.4274411,8.2458191 5.5025487,7.6524167 4.3866873,11 C3.5323884,13.562898 3.0594554,15.975595 2.3866875,18.666666 C1.0282598,24.100378 -1.137174,26.809351 0.72002077,33 C1.3220947,35.006912 3.1331961,37.458286 4.0533543,39.666668 C5.114325,42.212997 6.5792308,43.366936 9.7200203,44.333332 C15.411503,46.08456 20.752144,46 27.053354,46 C31.401215,46 34.719112,42.44838 36.053352,36.666668 C37.463272,30.557018 38.386688,25.955296 38.386688,19.333334 C38.386688,12.770806 34.890102,10.843581 33.053352,5.3333335 C29.067465,4.004704 25.056402,1.8348579 21.386688,3.1789145E-07 C21.386688,0.22222254 21.386688,0.44444478 21.386688,0.66666698 z" Fill="{x:Null}" Margin="7.28,9.333,13.333,3.666" Opacity="0" StrokeStartLineCap="Flat" StrokeEndLineCap="Flat" Stroke="Black" StrokeThickness="1" StrokeMiterLimit="10" StrokeLineJoin="Miter" UseLayoutRounding="False" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<CompositeTransform/>
</Path.RenderTransform>
</Path>
</Grid>
</UserControl>
In the end of this tutorial your custom control will look like that:

Same video on YouTube (two parts):