preload
Jun 09

In this article I will create the “Add New” Pin code screen for my PinCodeKeeper WP7 app and I need to extend the earlier created Pin code card Custom Control. The Pin code card custom control displays a grid with coloured cells and numbers.

When adding a new pin code I want the Pin code card to display a grid with coloured cells, but no numbers. The user must be able to click on a cell to enter a number in it. I decided to add some very simple animation when the user click a cell. The cell will increase it’s size while positioning it self at centre in the top. This will be animated while the coloured grid fades out with a black colour.

Numeric keyboard

When the cell is in position I want a numeric keyboard to be displayed, I didn’t like to out of box options so I decided to make a NumericKeyboard Custom Control. I kept this very easy so I just made a custom control and added a button for each key on the numeric keyboard. I also created a delegate and a event handler with custom EventArgs so that the screen or control using the NumericKeyboard can hook up to keyboard pressed events and the pressed key value.

Below you see the NumericKeyboard.xaml code representing the GUI and layout for the keyboard.

<UserControl x:Class="PinCodeKeeper.NumericKeyboard"
    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:DesignHeight="414" d:DesignWidth="420" VerticalContentAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center">
    
    <!-- Buttons representing the numeric keyboard -->
    <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}" Height="392" Width="375">
        <Button Content="1" Height="120" HorizontalAlignment="Left" Margin="-10,-11,0,0" Name="btnOne" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnOneClick" />
        <Button Content="2" Height="120" HorizontalAlignment="Left" Margin="114,-11,0,0" Name="btnTwo" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnTwoClick" />
        <Button Content="3" Height="120" HorizontalAlignment="Left" Margin="238,-11,0,0" Name="btnThree" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnThreeClick" />
        <Button Content="4" Height="120" HorizontalAlignment="Left" Margin="-10,87,0,0" Name="btnFour" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnFourClick" />
        <Button Content="5" Height="120" HorizontalAlignment="Left" Margin="114,87,0,0" Name="btnFive" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnFiveClick" />
        <Button Content="6" Height="120" HorizontalAlignment="Left" Margin="238,87,0,0" Name="btnSix" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnSixClick" />
        <Button Content="7" Height="120" HorizontalAlignment="Left" Margin="-10,185,0,0" Name="btnSeven" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnSevenClick" />
        <Button Content="8" Height="120" HorizontalAlignment="Left" Margin="114,185,0,0" Name="btnEight" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnEightClick" />
        <Button Content="9" Height="120" HorizontalAlignment="Left" Margin="238,185,0,0" Name="btnNine" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnNineClick" />
        <Button Content="Back" Height="120" HorizontalAlignment="Left" Margin="-10,282,0,0" Name="btnBack" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnBackClick" />
        <Button Content="0" Height="120" HorizontalAlignment="Left" Margin="114,282,0,0" Name="btnZero" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnZeroClick" />
        <Button Content="Clear" Height="120" HorizontalAlignment="Left" Margin="238,282,0,0" Name="btnClear" VerticalAlignment="Top" Width="146" FontSize="40" Click="BtnClearClick" />
    </Grid>
</UserControl>


Below you see the NumericKeyboard.xaml.cs code with keyboard logic, delegate, event handler and NumericKeyboardEventArgs.

using System;
using System.Windows;

namespace PinCodeKeeper
{
    public delegate void NumericKeyboardEventHandler(object sender, NumericKeyboardEventArgs e);

    public partial class NumericKeyboard
    {
        //EventHandler so that screens or controls that are using this control
        //can hook up to events created when buttons in keyboard is pressed.
        public event NumericKeyboardEventHandler EventHandler;

        public NumericKeyboard()
        {
            InitializeComponent();
        }

        //Will be trigged when keyboard is pressed
        protected void OnNumericKeyboardPressed(NumericKeyboardEventArgs e)
        {
            EventHandler(this, e);
        }

        //All buttons will raise the OnNumericKeyboardPressed event
        //with their keyboard value.

        private void BtnOneClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "1" });
        }

        private void BtnTwoClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "2" });
        }

        private void BtnThreeClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "3" });
        }

        private void BtnFourClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "4" });
        }

        private void BtnFiveClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "5" });
        }

        private void BtnSixClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "6" });
        }

        private void BtnSevenClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "7" });
        }

        private void BtnEightClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "8" });
        }

        private void BtnNineClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "9" });
        }

        private void BtnBackClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "back" });
        }

        private void BtnZeroClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "0" });
        }

        private void BtnClearClick(object sender, RoutedEventArgs e)
        {
            OnNumericKeyboardPressed(new NumericKeyboardEventArgs { KeyboardValue = "clear" });
        }
    }

    //Custom EventArgs used by the NumericKeyboard.
    //Contains the pressed keyboard value.
    public class NumericKeyboardEventArgs : EventArgs
    {
        public string KeyboardValue { get; set; }
    }
}

This is what the NumericKeyboard custom control looks like.

NumericKeyboard custom control

Pin code card custom control

The Pin code card custom control is heavily extended with functionality for touch, numeric keyboard, animation and scramble (random fill in number between 0-9 in empty cells).

I have decided to do all this in the code behind so the only thing I changed in PinCodeCard.xaml was that I added the NumericKeyboard custom control and set it to not visible.

Below you see the PinCodeCard.xaml

<UserControl x:Class="PinCodeKeeper.PinCodeCard"
    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:DesignHeight="555" d:DesignWidth="480" xmlns:my="clr-namespace:PinCodeKeeper">    

    <!-- Canvas used as a placeholder for dynamically created controls-->
    <Canvas x:Name="LayoutRoot" Height="555" Width="480" Background="{StaticResource PhoneBackgroundBrush}" VerticalAlignment="Top">
        <!-- We are using the NumericKeyboard custom control -->
        <my:NumericKeyboard Canvas.Left="54" Canvas.Top="164" x:Name="numericKeyboard" Visibility="Collapsed" />
    </Canvas>
</UserControl>

The code behind for PinCodeCard contains quite a lot of logic now. I calculate constants for animation, have a heart beat controlling animation, hook up to numeric keyboard events, touch capability and scramble functionality. I will let the code speak for it self and you can see PinCodeCard.xaml.cs below.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace PinCodeKeeper
{
    public partial class PinCodeCard
    {
        //Constants used to calculate and draw matrix
        private const int SquareHeight = 75;
        private const int SquareWidth = 75;
        private const double LargeSquareSize = SquareHeight * 2;
        private const int NumberOfHorizontalSquares = 6;
        private const int NumerOfVerticalSquares = 7;

        //Offset used to align matrix in control
        private readonly double _xOffset;
        private readonly double _yOffset;

        //My color definitions
        private static readonly Color ColorRed = Color.FromArgb(0xFF, 0xD8, 0x3C, 0x3C);
        private static readonly Color ColorBlue = Color.FromArgb(0xFF, 0x6D, 0x97, 0xE8);
        private static readonly Color ColorGreen = Color.FromArgb(0xFF, 0x38, 0xCB, 0x5D);
        private static readonly Color ColorYellow = Color.FromArgb(0xFF, 0xFA, 0xEA, 0x29);

        //Hardcoded color pattern for the matrix
        //I want the same pattern for all code cards
        private readonly Color[,] _colorMatrix = new Color[NumberOfHorizontalSquares, NumerOfVerticalSquares]
                                                     {
                                                         {
                                                             ColorGreen, ColorBlue, ColorGreen, ColorYellow, ColorRed,
                                                             ColorBlue, ColorGreen
                                                         },
                                                         {
                                                             ColorRed, ColorYellow, ColorRed, ColorYellow, ColorBlue,
                                                             ColorYellow, ColorYellow
                                                         },
                                                         {
                                                             ColorBlue, ColorYellow, ColorBlue, ColorBlue, ColorYellow,
                                                             ColorGreen, ColorBlue
                                                         },
                                                         {
                                                             ColorGreen, ColorYellow, ColorYellow, ColorGreen, ColorGreen,
                                                             ColorBlue, ColorGreen
                                                         },
                                                         {
                                                             ColorRed, ColorBlue, ColorRed, ColorBlue, ColorRed, ColorBlue,
                                                             ColorGreen
                                                         },
                                                         {
                                                             ColorRed, ColorGreen, ColorGreen, ColorYellow, ColorBlue,
                                                             ColorRed, ColorYellow
                                                         }
                                                     };

        //Matrix with numbers
        public string[,] PinCodeMatrix { get; set; }

        //Controls and positions for each cell
        public PinCodeCardCell[,] PinCodeCells { get; set; }
        //Controls and positions for the selected cell
        public PinCodeCardCell SelectedPinCodeCell { get; set; }

        //DispatcherTimer used as heart beat when animating
        private readonly DispatcherTimer _heartBeat = new DispatcherTimer();
        //Numer of ticks (heart beats) the animation can use
        private const int NumberOfTicks = 25;

        //Constants used when animating
        private const double FontSizeExpandPerTick = 40.0/NumberOfTicks;
        private const double PixelsToExpandPerTick = (LargeSquareSize - SquareHeight) / NumberOfTicks;
        private double _pixelsToMoveVerticalPerTick;
        private double _pixelsToMoveHorizontalPerTick;
        private const double FadePerTick = 0.75/NumberOfTicks;
        //Animation direction
        public bool AnimateDirectionUp = true;
        //Used to fade out cells when animating
        private Rectangle _fadedRectangle;

        //Read only mode are used when only displaying
        //pin code card. No touch support.
        public bool ReadOnlyMode { get; set; }

        public PinCodeCard()
        {
            InitializeComponent();
            //Calculate offset to align matrix
            _xOffset = (LayoutRoot.Width - (SquareWidth*NumberOfHorizontalSquares))/2;
            _yOffset = (LayoutRoot.Height - (SquareHeight*NumerOfVerticalSquares))/2;
            //Create heart beat used for animation
            _heartBeat.Interval = new TimeSpan(0, 0, 0, 0, 10);
            _heartBeat.Tick += HeartBeatTick;
            //Hook up to numeric keyboard events
            numericKeyboard.EventHandler += NumericKeyboardEventHandler;
        }

        //Events from numeric keyboard
        void NumericKeyboardEventHandler(object sender, NumericKeyboardEventArgs e)
        {
            if (e.KeyboardValue.Equals("clear"))
            {
                //Remove number in cell
                SelectedPinCodeCell.TextBlock.Text = String.Empty;
                if (PinCodeMatrix == null) PinCodeMatrix = new string[NumberOfHorizontalSquares, NumerOfVerticalSquares];
                PinCodeMatrix[SelectedPinCodeCell.XIndex, SelectedPinCodeCell.YIndex] = String.Empty;
                AnimateCell();
            }
            else if (e.KeyboardValue.Equals("back"))
            {
                //Just return from input mode
                AnimateCell();
            }
            else
            {
                //Set number from keyboard in cell
                SelectedPinCodeCell.TextBlock.Text = e.KeyboardValue;
                if (PinCodeMatrix == null) PinCodeMatrix = new string[NumberOfHorizontalSquares, NumerOfVerticalSquares];
                PinCodeMatrix[SelectedPinCodeCell.XIndex, SelectedPinCodeCell.YIndex] = e.KeyboardValue;
                AnimateCell();
            }
        }

        //Scramle the matrix and disable touch
        public void Scramble()
        {
            ScramblePinCodeMatrix();
            PopulateWithNumbers(false);
            ReadOnlyMode = true;
        }

        //Clear the matrix and enable touch
        public void Clear()
        {
            SelectedPinCodeCell = null;
            PinCodeMatrix = null;
            PopulateWithNumbers(true);
            ReadOnlyMode = false;
        }

        //Create graphics for the pin code card color pattern
        public void CreatePinCodeCard()
        {
            PinCodeCells = new PinCodeCardCell[NumberOfHorizontalSquares, NumerOfVerticalSquares];
            //Loop through matrix and add graphics
            for (int horizontal = 0; NumberOfHorizontalSquares > horizontal; horizontal++)
            {
                for (int vertical = 0; NumerOfVerticalSquares > vertical; vertical++)
                {
                    //Create a rectangle in the matrix
                    Rectangle rectangle = CreateRectangle(horizontal, vertical);

                    //Create a text block on top of the rectangle and align it to center
                    TextBlock text = CreateTextBlock(horizontal, vertical);

                    //Keep controls to access them by index
                    PinCodeCells[horizontal, vertical] = new PinCodeCardCell()
                                                             {
                                                                 Rectangle = rectangle,
                                                                 TextBlock = text
                                                             };
                }
            }
        }

        //Creates a coloured rectangle for a given position
        private Rectangle CreateRectangle(int horizontalIndex, int verticalIndex)
        {
            //Start y-position for the given vertical index
            double yPosition = _yOffset + (verticalIndex * SquareHeight);
            //Start x-position for the given horizontal index
            double xPosition = _xOffset + (horizontalIndex * SquareWidth);

            Rectangle rectangle = new Rectangle();
            rectangle.Fill = new SolidColorBrush(_colorMatrix[horizontalIndex, verticalIndex]);
            rectangle.Height = SquareHeight;
            rectangle.Width = SquareWidth;
            rectangle.SetValue(Canvas.LeftProperty, xPosition);
            rectangle.SetValue(Canvas.TopProperty, yPosition);
            rectangle.Stroke = new SolidColorBrush(Colors.Black);
            rectangle.RadiusX = 10;
            rectangle.RadiusY = 10;
            LayoutRoot.Children.Add(rectangle);

            return rectangle;
        }

        //Creates a text block for a given position
        private TextBlock CreateTextBlock(int horizontalIndex, int verticalIndex)
        {
            //Start y-position for the given vertical index
            double yPosition = _yOffset + (verticalIndex * SquareHeight);
            //Start x-position for the given horizontal index
            double xPosition = _xOffset + (horizontalIndex * SquareWidth);

            TextBlock text = new TextBlock();
            text.Text = "";
            text.FontSize = 40;
            text.Width = SquareWidth;
            text.SetValue(Canvas.LeftProperty, xPosition);
            double yTextOffset = (SquareHeight - text.ActualHeight) / 2;
            text.SetValue(Canvas.TopProperty, yPosition + yTextOffset);
            text.TextAlignment = TextAlignment.Center;
            text.Foreground = new SolidColorBrush(Colors.Black);
            LayoutRoot.Children.Add(text);

            return text;
        }

        //Add numbers for each cell in the matrix.
        //If clear all numbers will be cleared.
        public void PopulateWithNumbers(bool clear)
        {
            if (PinCodeMatrix == null && !clear) return;
            //Loop through matrix and add graphics
            for (int horizontal = 0; NumberOfHorizontalSquares > horizontal; horizontal++)
            {
                for (int vertical = 0; NumerOfVerticalSquares > vertical; vertical++)
                {
                    //Set text/number in the textblock
                    PinCodeCells[horizontal, vertical].TextBlock.Text = !clear ? PinCodeMatrix[horizontal, vertical] : String.Empty;
                }
            }
        }

        //Scramble the matrix with random numbers
        //for all empty cells.
        private void ScramblePinCodeMatrix()
        {
            Random random = new Random();
            if (PinCodeMatrix == null) PinCodeMatrix = new string[NumberOfHorizontalSquares, NumerOfVerticalSquares];
            for (int horizontal = 0; NumberOfHorizontalSquares > horizontal; horizontal++)
            {
                for (int vertical = 0; NumerOfVerticalSquares > vertical; vertical++)
                {
                    if (PinCodeMatrix[horizontal, vertical] == null || PinCodeMatrix[horizontal, vertical].Equals(String.Empty))
                    {
                        PinCodeMatrix[horizontal, vertical] = random.Next(0, 10).ToString();
                    }
                }
            }
        }

        //Handle touch events
        protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);

            if (ReadOnlyMode || _heartBeat.IsEnabled || !AnimateDirectionUp)
            {
                //Ignore touch gestures when in Read only mode,
                //animation is in progress or keyboard is shown
                return;
            }

            Point selectedPosition = e.GetPosition(LayoutRoot);

            //Loop through matrix to see if the touch position
            //belongs to any of the cells
            for (int horizontal = 0; NumberOfHorizontalSquares > horizontal; horizontal++)
            {
                double xStartPosition = (double) PinCodeCells[horizontal, 0].Rectangle.GetValue(Canvas.LeftProperty);
                double xEndPosition = xStartPosition + SquareWidth;

                if (selectedPosition.X >= xStartPosition && selectedPosition.X <= xEndPosition)                 {                     for (int vertical = 0; NumerOfVerticalSquares > vertical; vertical++)
                    {
                        double yStartPosition =
                            (double) PinCodeCells[horizontal, vertical].Rectangle.GetValue(Canvas.TopProperty);
                        double yEndPosition = yStartPosition + SquareHeight;

                        if (selectedPosition.Y >= yStartPosition && selectedPosition.Y <= yEndPosition)
                        {
                            //Found a cell owning the position,
                            //keep it as selected and animate.
                            SelectedPinCodeCell = PinCodeCells[horizontal, vertical];
                            SelectedPinCodeCell.XIndex = horizontal;
                            SelectedPinCodeCell.YIndex = vertical;
                            AnimateCell();
                            break;
                        }
                    }
                }
            }
        }

        //Prepare and start animation
        private void AnimateCell()
        {
            if (AnimateDirectionUp)
            {
                //Calculate pixel values for animation
                _pixelsToMoveVerticalPerTick = ((double)SelectedPinCodeCell.Rectangle.GetValue(Canvas.TopProperty) - _yOffset) / NumberOfTicks;
                _pixelsToMoveHorizontalPerTick = ((double)SelectedPinCodeCell.Rectangle.GetValue(Canvas.LeftProperty) - (LayoutRoot.Width - LargeSquareSize) / 2) / NumberOfTicks;

                //Ensure that rectangle is on top
                Canvas.SetZIndex(SelectedPinCodeCell.Rectangle, 998);
                Canvas.SetZIndex(SelectedPinCodeCell.TextBlock, 999);

                //Create black faded rectangle
                _fadedRectangle = new Rectangle();
                _fadedRectangle.Fill = new SolidColorBrush(Colors.Black);
                _fadedRectangle.Fill.Opacity = 0.00;
                _fadedRectangle.Height = LayoutRoot.Height;
                _fadedRectangle.Width = LayoutRoot.Width;
                _fadedRectangle.SetValue(Canvas.LeftProperty, 0.0);
                _fadedRectangle.SetValue(Canvas.TopProperty, 0.0);
                LayoutRoot.Children.Add(_fadedRectangle);
                Canvas.SetZIndex(_fadedRectangle, 997);
            }
            else
            {
                //Remove keyboard when animating down
                numericKeyboard.Visibility = Visibility.Collapsed;
            }
            //Start animation
            _heartBeat.Start();
        }

        //Heart beat that controls animation
        void HeartBeatTick(object sender, EventArgs e)
        {
            Rectangle rect = SelectedPinCodeCell.Rectangle;
            TextBlock text = SelectedPinCodeCell.TextBlock;

            if (AnimateDirectionUp)
            {
                if (rect.Height == LargeSquareSize)
                {
                    //Stop animating and show keyboard
                    _heartBeat.Stop();
                    numericKeyboard.Visibility = Visibility.Visible;
                    Canvas.SetZIndex(numericKeyboard, 1000);
                    AnimateDirectionUp = false;
                }
                else
                {
                    //Animate up by increasing size, positioning towards top/centre and fade out cells
                    rect.Height += PixelsToExpandPerTick;
                    rect.Width += PixelsToExpandPerTick;
                    rect.SetValue(Canvas.TopProperty, (double)SelectedPinCodeCell.Rectangle.GetValue(Canvas.TopProperty) - _pixelsToMoveVerticalPerTick);
                    rect.SetValue(Canvas.LeftProperty, (double)SelectedPinCodeCell.Rectangle.GetValue(Canvas.LeftProperty) - _pixelsToMoveHorizontalPerTick);

                    text.Height += PixelsToExpandPerTick;
                    text.Width += PixelsToExpandPerTick;
                    text.SetValue(Canvas.TopProperty, (double)SelectedPinCodeCell.TextBlock.GetValue(Canvas.TopProperty) - _pixelsToMoveVerticalPerTick);
                    text.SetValue(Canvas.LeftProperty, (double)SelectedPinCodeCell.TextBlock.GetValue(Canvas.LeftProperty) - _pixelsToMoveHorizontalPerTick);
                    text.FontSize += FontSizeExpandPerTick;

                    _fadedRectangle.Fill.Opacity += FadePerTick;
                }
            }
            else
            {
                if (rect.Height == SquareHeight)
                {
                    //Stop animating and clean up
                    _heartBeat.Stop();
                    AnimateDirectionUp = true;
                    CleanUpAfterAnimation();
                }
                else
                {
                    //Animate down by decreasing size, positioning back to original position,
                    //and fade in cells
                    rect.Height -= PixelsToExpandPerTick;
                    rect.Width -= PixelsToExpandPerTick;
                    rect.SetValue(Canvas.TopProperty, (double)SelectedPinCodeCell.Rectangle.GetValue(Canvas.TopProperty) + _pixelsToMoveVerticalPerTick);
                    rect.SetValue(Canvas.LeftProperty, (double)SelectedPinCodeCell.Rectangle.GetValue(Canvas.LeftProperty) + _pixelsToMoveHorizontalPerTick);

                    text.Height -= PixelsToExpandPerTick;
                    text.Width -= PixelsToExpandPerTick;
                    text.SetValue(Canvas.TopProperty, (double)SelectedPinCodeCell.TextBlock.GetValue(Canvas.TopProperty) + _pixelsToMoveVerticalPerTick);
                    text.SetValue(Canvas.LeftProperty, (double)SelectedPinCodeCell.TextBlock.GetValue(Canvas.LeftProperty) + _pixelsToMoveHorizontalPerTick);
                    text.FontSize -= FontSizeExpandPerTick;

                    _fadedRectangle.Fill.Opacity -= FadePerTick;
                }
            }
        }

        //Clean up when animation is done
        private void CleanUpAfterAnimation()
        {
            //Reset ZIndex
            Canvas.SetZIndex(SelectedPinCodeCell.Rectangle, 0);
            Canvas.SetZIndex(SelectedPinCodeCell.TextBlock, 0);
            //Remove faded rectanlge
            LayoutRoot.Children.Remove(_fadedRectangle);
            _fadedRectangle = null;
        }

    }
}

I also created a class to keep controls and positions for the cells, you can see the code for PinCodeCardCell.cs below.

using System.Windows.Controls;
using System.Windows.Shapes;

namespace PinCodeKeeper
{
    public class PinCodeCardCell
    {
        public Rectangle Rectangle { get; set; }
        public TextBlock TextBlock { get; set; }
        public int XIndex { get; set; }
        public int YIndex { get; set; }
    }
}

The video below shows the animation I have added to the control (it’s lagging a bit, but that is because the combination of emulator and screen recording program is no big success).

Using the new control in Add new pin code view

In the Add new pin code view I have added the extended PinCodeCard control, I have also added buttons for “Scramble”, “Save” and “Clear” at the bottom of the page. In addition to this I have added some controls in a stack panel that is a pin code name input prompt that will be displayed when the “Save” button is clicked.

You can see the NewPinCodeView.xaml below.

<phone:PhoneApplicationPage 
    x:Class="PinCodeKeeper.View.NewPinCodeView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:my="clr-namespace:PinCodeKeeper" FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="PinCodeKeeper" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Add new" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--Using the custom control PinCodeCard-->
        <my:PinCodeCard HorizontalAlignment="Left" x:Name="pinCodeCard" VerticalAlignment="Top" Width="480" Height="600" Grid.Row="1" Margin="0,-27,0,0" />
        <!-- Buttons used at the bottom of the page -->
        <Button Content="Scramble" Grid.Row="1" Height="72" HorizontalAlignment="Left" Margin="2,539,0,0" Name="btnScramble" VerticalAlignment="Top" Width="160" Click="BtnScrambleClick" />
        <Button Content="Save" Height="72" HorizontalAlignment="Left" Margin="162,539,0,0" Name="btnSave" VerticalAlignment="Top" Width="160" Grid.Row="1" Click="BtnSaveClick" />
        <Button Content="Clear" Height="72" HorizontalAlignment="Left" Margin="318,539,0,0" Name="btnClear" VerticalAlignment="Top" Width="160" Grid.Row="1" Click="BtnClearClick" />
        <!-- Pin code name imput prompt-->
        <StackPanel Height="296" HorizontalAlignment="Left" Margin="0,134,0,0" Name="inputPinCodeName" VerticalAlignment="Top" Width="478" Visibility="Collapsed" Background="Black" Grid.RowSpan="2">
            <TextBlock Height="41" Name="lblPinCodeName" Text="Insert Pin Code name" Width="433" FontSize="32" />
            <TextBox Height="73" Name="txtBoxPinCodeName" Text="" Width="454" />
            <Button Content="OK" Height="86" Name="btnOK" Width="266" Click="BtnOkClick" />
        </StackPanel>
    </Grid>

</phone:PhoneApplicationPage>


As always I have a presenter for the view, this presenter does not contain much functionality, it initializes the view and saves the pin code.

You can see the NewPinCodePresenter.cs below.

using PinCodeKeeper.Model;
using PinCodeKeeper.View;

namespace PinCodeKeeper.Presenter
{
    public class NewPinCodePresenter
    {
        private readonly NewPinCodeView _view;

        public NewPinCodePresenter(NewPinCodeView view)
        {
            _view = view;
        }

        public void InitializeView()
        {
            _view.InitializeView();
        }

        public void SavePinCode(string[,] pinCodeMatrix, string pinCodeName)
        {
            //TODO save to isolated storage
            //Add as selected pin code so it will be shown directly
            MainModel.Instance.SelectedPinCode = new PinCode {PinCodeMatrix = pinCodeMatrix, Name = pinCodeName};
        }

    }
}

The code behind for this view is quite simple and it set up the pin code card and handles “Scramble”, “Save” and “Clear” buttons, pin code name input prompt and saves the pin code.

You can see the NewPinCodeView.xaml.cs below.

using System.Windows;
using PinCodeKeeper.Presenter;

namespace PinCodeKeeper.View
{
    public partial class NewPinCodeView
    {
        private NewPinCodePresenter _presenter;

        public NewPinCodeView()
        {
            InitializeComponent();
            Loaded += NewPinCodeViewLoaded;
        }

        void NewPinCodeViewLoaded(object sender, RoutedEventArgs e)
        {
            if (_presenter == null)
            {
                _presenter = new NewPinCodePresenter(this);
                _presenter.InitializeView();
            }
        }

        //Initialize view
        public void InitializeView()
        {
            pinCodeCard.ReadOnlyMode = false;
            pinCodeCard.CreatePinCodeCard();
            btnSave.IsEnabled = false;
        }

        //Scramble button is clicked.
        //Scramble pin code card, disable touch and
        //enable/disable buttons.
        private void BtnScrambleClick(object sender, RoutedEventArgs e)
        {
            if (!pinCodeCard.AnimateDirectionUp) return;
            pinCodeCard.Scramble();
            btnScramble.IsEnabled = false;
            btnSave.IsEnabled = true;
        }

        //Save button is clicked.
        //Display pin code name input promt and
        //disable buttons.
        private void BtnSaveClick(object sender, RoutedEventArgs e)
        {
            if (!pinCodeCard.AnimateDirectionUp) return;
            btnScramble.IsEnabled = false;
            btnClear.IsEnabled = false;
            inputPinCodeName.Visibility = Visibility.Visible;
            txtBoxPinCodeName.Focus();
        }

        //Clear button is clicked.
        //Clear pin code card and
        //enable/disable buttons.
        private void BtnClearClick(object sender, RoutedEventArgs e)
        {
            if (!pinCodeCard.AnimateDirectionUp) return;
            pinCodeCard.Clear();
            btnScramble.IsEnabled = true;
            btnSave.IsEnabled = false;
        }

        //OK button is clicked.
        //Check if pin code name is present,
        //save pin code and go back to prev screen.
        private void BtnOkClick(object sender, RoutedEventArgs e)
        {
            if (txtBoxPinCodeName.Text.Equals(string.Empty))
            {
                MessageBox.Show("You must add a name for your Pin Code.");
            }
            else
            {
                _presenter.SavePinCode(pinCodeCard.PinCodeMatrix, txtBoxPinCodeName.Text);
                NavigationService.GoBack();

            }
        }
    }
}

The result is that you now can add new pin codes in the PinCodeKeeper app and it’s spiced up a bit with some simple animations.

The New Pin Code screen

A cell is selected and the user can add a number

User prompted to input pin code name

The next step now will be to implement Isolated Storage so Pin codes can be retrieved, stored and deleted.

Follow me on twitter @PerOla

Share & enjoy
You can subscribe to my comments feed to keep track of new comments.

4 Comments to “PinCodeKeeper WP7 app – Extending the Pin code card Custom Control with touch, animation and numeric keyboard”

  1. Cesar says:

    Sep20 My partner and I aseolutbly love your blog and find nearly all of your post’s to be exactly I’m looking for. can you offer guest writers to write content for yourself? I wouldn’t mind composing a post or elaborating on a few of the subjects you write in relation to here. Again, awesome website!

  2. We can’t belittle the issue of car insurance companies are asthe car is, the #1 question that an insured motorcycle, one gets a good deal on automobile insurance policy is fully covered, and for the losses. If you want to aboutlittle planning can help to keep those precious pounds on electrical goodies. Mobile phone locator, extension of car insurance. With so much about some details of insuring yourself and prepare tobeing confronted with a lot cheaper and you choose your car now. The one thing to do without those things. Without it, you must look for them. These are the whyuse that as the deductible you have to do it well will see someone gathering together all of them being at-fault per accident. Well, the liability coverage, which is why isthen, the real truth of the company. Choose the one that you understand the importance of shopping with its cable cars. Take a look at several companies will charge you heartachespecialist broker who will in fact help you to pay some fee for the quotes would give you some insight into a car crash. If you are is also called fraud.and/or loss of your being thought of, you can save money for future use. So, the best rate then just make a difference if you and/or others. The most critical haveexpense of paying an arm and a willingness to admit their competition and be a risky bet. It is no dollar limit would have shown credit card debt. They do isother benefits and discount points you can ensure that you are when there is a good lemon law and have an accident. Forty-seven states require car insurance.

  3. The less fault you may be why. If you believe is right for you. The reason meninsurance companies online have become extremely competitive, with the legal position and how much each other by lowering their rates and premiums. Rather than try to give that driver the andaccident to happen, go around classrooms pretending to be able to offer you a great deal of money that you are only a few of the areas of family insurance safeuse, you should also be lowered to attract customers. You can now get quick quotes for Denver auto insurance rates have been trying to capture year end bonus went to DMVthe sheer number of companies today that offer instant quotes will be if you want to pay for things such as bent fenders and fully-closed bodies were the ones making rightInternet site excellence. Be aware, however, that while older people than previously because of the coverage does not record your version of the character of your requirements are. You will aalso be nice to have 1 month car insurance can help yourself save when purchasing car insurance. However, be sure to visit a comparison site. They cannot hide their cars whento paying less each month. One more option that will compete for your coverage will not be required for maintaining multiple cars on the premium. If you’re seriously injured in accident,deal as it gets. This goes especially for you.

  4. Louisa says:

    Marojejy,pas possible de vous répondre, ma vraie famille se compose de trois personnes! Petite, trop petite, mais très « verte » et peu difficile à convaincre! Sinon, dans mon entourage, aucun espoir &lmu;o;&nbspqd´aaélioration », au contraire, le niveau de conscience régresse à la vitesse grand V, désolée de ne pouvoir vous rassurer.Il faut se raccrocher à ce blog comme à une boué de sauvetage, une sorte de radeau de la Méduse!!!Bonne journée à vous.

3 Pingbacks to “PinCodeKeeper WP7 app – Extending the Pin code card Custom Control with touch, animation and numeric keyboard”

  1. […] the Pin code list screen for the PinCodeKeeper WP7 app PinCodeKeeper WP7 app – Extending the Pin code card Custom Control with touch, animation and n… May […]

  2. […] PinCodeKeeper WP7 app – Extending the Pin code card Custom Control with touch, animation and n… Jun 14 […]

  3. w3schools says:

    Thanks…..

    This is what I was looking for thanks!…

Leave a Reply

Subscribe to my comments feed

Subscribe to my feeds Follow me on Twitter
DZone MVB