Programmatic Drawing with Silverlight 2.0/3.0 – A Digital Clock

This is the second in a small series I am writing on programmatic drawing inside Silverlight.  The first post I did on this was an analogue clock and it can be found here:

http://www.andrewrea.co.uk/blog/2009/08/12/ProgrammaticDrawingWithSilverlight2030AnAnalogueClock.aspx

The code for this can be downloaded from the following link: http://www.andrewrea.co.uk/DigitalClock.rar

In this post I will create a digital clock, with the disjointed digits you see in those stereotypical red digit alarm clocks.  The way I tackled this was look at the individual component parts and their grouping to ultimately give the representation of time or any other numerical value.  So each digit of the display is made up of individual bars with angle ends.  One of the key things with the angle ends, is that they are at a 45 degree angle, so that when brought together they line up with one another.

Image

image

Working In Silverlight

The parts of this inline with the code files are as follows:

  • IDigitalDigitPiece
  • IDigitalDigit
  • IDigitalState
  • IDigitalClockView

The second one in the list is the Digit State.  From the above examples you can see that each digit is made up of 7 component pieces, and each one represents their assigned digit by using different combinations of these digits.  In this case I have assigned two properties to each Digit being the OnColor and the OffColor.

The IDigitState has the following signature:

    public interface IDigitState
    {
        void Handle(IDigitalDigit digit);
    }

An example of one of the states is the following.  They simply assign the the OffColor or the OnColor to the Color property of the DigitalDigitPiece.

 

    public class FourState : IDigitState
    {

        #region IDigitState Members

        public void Handle(IDigitalDigit digit)
        {
            digit.DigitalDigitPieces[0].DigitColor = digit.OffColor;
            digit.DigitalDigitPieces[1].DigitColor = digit.OnColor;
            digit.DigitalDigitPieces[2].DigitColor = digit.OnColor;
            digit.DigitalDigitPieces[3].DigitColor = digit.OnColor;
            digit.DigitalDigitPieces[4].DigitColor = digit.OffColor;
            digit.DigitalDigitPieces[5].DigitColor = digit.OnColor;
            digit.DigitalDigitPieces[6].DigitColor = digit.OffColor;
        }

        #endregion
    }

 

I have pasted both the code for the digit and the digit piece below.  In hind sight I think it would have been better to use examples which display the binding.  The digit code displays how I am positioning the pieces after any resize or load events and the digit piece displays the code which shows how I am creating the shape of the pieces which make up the digit.

public partial class DigitalDigit : UserControl, IDigitalDigit
    {
        public List<DigitalDigitPiece> DigitalDigitPieces { get; protected set; }

        private double _sepDiameter;

        public double SepDiameter
        {
            get { return _sepDiameter; }
            set
            {
                if (value != _sepDiameter)
                {
                    _sepDiameter = value;
                    Draw();
                }
            }
        }

        private Color _sepColor;

        public Color SepColor
        {
            get { return _sepColor; }
            set
            {
                if (value != _sepColor)
                {
                    _sepColor = value;
                    Draw();
                }
            }
        }

        private Color _onColor = Colors.Red;

        public Color OnColor
        {
            get { return _onColor; }
            set { _onColor = value; }
        }

        private Color _offColor = Colors.Black;

        public Color OffColor
        {
            get { return _offColor; }
            set { _offColor = value; }
        }

        private double _pointSize = 10;

        public double PointSize
        {
            get { return _pointSize; }
            set
            {
                if (value != _pointSize)
                {
                    _pointSize = value;
                    Draw();
                }
            }
        }

        private Color _digitColor = Colors.Red;

        public Color DigitColor
        {
            get { return _digitColor; }
            set
            {
                if (value != _digitColor)
                {
                    _digitColor = value;
                    Draw();
                }
            }
        }

        public DigitalDigit()
        {
            InitializeComponent();
            CreateDigits();
            SizeChanged += new SizeChangedEventHandler(DigitalDigit_SizeChanged);
            Loaded += new RoutedEventHandler(DigitalDigit_Loaded);
        }

        void DigitalDigit_Loaded(object sender, RoutedEventArgs e)
        {
            Draw();
        }

        void DigitalDigit_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Draw();
        }

        protected void Draw()
        {
            foreach (var digit in DigitalDigitPieces)
            {
                digit.PointSize = PointSize;
            }

            AlignDigits();
        }

        protected double LongHeight
        {
            get
            {
                return (ActualHeight / 2) - _pointSize;
            }
        }

        protected void AlignDigits()
        {
            if (ActualWidth > 0 && ActualHeight > 0)
            {
                DigitalDigitPieces[0].Orientation = DigitalDigitPieceOrientation.Horizontal;
                DigitalDigitPieces[0].Height = ActualWidth - (DigitalDigitPieces[0].PointSize * 2);
                DigitalDigitPieces[0].SetValue(Canvas.LeftProperty, DigitalDigitPieces[0].PointSize);
                DigitalDigitPieces[0].PointSize = PointSize;

                DigitalDigitPieces[1].SetValue(Canvas.LeftProperty, 0D);
                DigitalDigitPieces[1].Height = LongHeight;
                DigitalDigitPieces[1].SetValue(Canvas.TopProperty, (double)DigitalDigitPieces[0].Width / 2);
                DigitalDigitPieces[1].PointSize = PointSize;

                DigitalDigitPieces[2].SetValue(Canvas.LeftProperty, (double)ActualWidth - DigitalDigitPieces[2].Width);
                DigitalDigitPieces[2].Height = LongHeight;
                DigitalDigitPieces[2].SetValue(Canvas.TopProperty, (double)DigitalDigitPieces[0].Width / 2);
                DigitalDigitPieces[2].PointSize = PointSize;

                DigitalDigitPieces[3].Orientation = DigitalDigitPieceOrientation.Horizontal;
                DigitalDigitPieces[3].Height = ActualWidth - (DigitalDigitPieces[3].PointSize * 2);
                DigitalDigitPieces[3].SetValue(Canvas.LeftProperty, DigitalDigitPieces[3].PointSize);
                DigitalDigitPieces[3].SetValue(Canvas.TopProperty, (((double)DigitalDigitPieces[2].GetValue(Canvas.TopProperty)) + (double)(DigitalDigitPieces[2].Height - DigitalDigitPieces[2].PointSize)));
                DigitalDigitPieces[3].PointSize = PointSize;

                DigitalDigitPieces[4].SetValue(Canvas.LeftProperty, 0D);
                DigitalDigitPieces[4].Height = LongHeight;
                DigitalDigitPieces[4].SetValue(Canvas.TopProperty, (((double)DigitalDigitPieces[3].GetValue(Canvas.TopProperty)) + (double)(DigitalDigitPieces[3].Width / 2)));
                DigitalDigitPieces[4].PointSize = PointSize;

                DigitalDigitPieces[5].SetValue(Canvas.LeftProperty, (double)ActualWidth - DigitalDigitPieces[4].Width);
                DigitalDigitPieces[5].Height = LongHeight;
                DigitalDigitPieces[5].SetValue(Canvas.TopProperty, (((double)DigitalDigitPieces[3].GetValue(Canvas.TopProperty)) + (double)(DigitalDigitPieces[3].Width / 2)));
                DigitalDigitPieces[5].PointSize = PointSize;

                DigitalDigitPieces[6].Orientation = DigitalDigitPieceOrientation.Horizontal;
                DigitalDigitPieces[6].Height = ActualWidth - (DigitalDigitPieces[6].PointSize * 2);
                DigitalDigitPieces[6].SetValue(Canvas.LeftProperty, DigitalDigitPieces[3].PointSize);
                DigitalDigitPieces[6].SetValue(Canvas.TopProperty, (((double)DigitalDigitPieces[5].GetValue(Canvas.TopProperty)) + (double)(DigitalDigitPieces[5].Height - DigitalDigitPieces[5].PointSize)));
                DigitalDigitPieces[6].PointSize = PointSize;
            }
        }

        protected void CreateDigits()
        {
            DigitalDigitPieces = new List<DigitalDigitPiece>();
            for (var i = 0; i < 7; i++)
            {
                var piece = new DigitalDigitPiece();
                DigitalDigitPieces.Add(piece);
                LayoutRoot.Children.Add(DigitalDigitPieces[i]);
            }
        }

        #region IDigitalDigit Members

        public void SetState(IDigitState state)
        {
            state.Handle(this);
        }

        #endregion

        public void Update()
        {
            Draw();
        }
    }

 

 public partial class DigitalDigitPiece : UserControl
    {
        private double _angle = 0D;

        private double _pointSize = 10;

        public double PointSize
        {
            get { return _pointSize; }
            set
            {
                if (value != _pointSize)
                {
                    _pointSize = value;
                    Draw();
                }
            }
        }

        private Color _digitColor;

        public Color DigitColor
        {
            get { return _digitColor; }
            set
            {
                if (value != _digitColor)
                {
                    _digitColor = value;
                    Draw();
                }
            }
        }

        private DigitalDigitPieceOrientation _orientation;

        public DigitalDigitPieceOrientation Orientation
        {
            get { return _orientation; }
            set
            {
                if (value != _orientation)
                {
                    _orientation = value;
                    Draw();
                }
            }
        }

        private Polygon _polygon;

        public Polygon Polygon
        {
            get { return _polygon; }
            set { _polygon = value; }
        }

        public DigitalDigitPiece()
        {
            _digitColor = Colors.Red;
            _orientation = DigitalDigitPieceOrientation.Vertical;
            _polygon = new Polygon();

            InitializeComponent();
            SizeChanged += new SizeChangedEventHandler(DigitalDigitPiece_SizeChanged);
            Loaded += new RoutedEventHandler(DigitalDigitPiece_Loaded);

            LayoutRoot.Children.Add(_polygon);
        }

        void DigitalDigitPiece_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Draw();
        }

        void DigitalDigitPiece_Loaded(object sender, RoutedEventArgs e)
        {
            Draw();
        }

        protected void Draw()
        {
            Width = _pointSize * 2;

            _polygon.Points = new PointCollection
            {
                 new Point(Width / 2,0),
                 new Point(Width, _pointSize),
                 new Point(Width, Height - _pointSize),
                 new Point(Width/2,Height),
                 new Point(0,Height-_pointSize),
                 new Point(0, _pointSize),
                 new Point(Width/2,0)
            };
            _polygon.Fill = new SolidColorBrush(DigitColor);
            _polygon.Stroke = new SolidColorBrush(Colors.Black);
            _polygon.StrokeThickness = 1D;

            var rotate = new RotateTransform();
            rotate.CenterX = _pointSize;
            rotate.CenterY = _pointSize;
            if (_orientation == DigitalDigitPieceOrientation.Horizontal)
            {
                if (_angle == 0 || _angle == 90)
                    _angle = -90;
            }
            else
            {
                if (_angle == -90)
                    _angle = 90;
                else
                    _angle = 0;
            }

            rotate.Angle = _angle;
            _polygon.RenderTransform = rotate;

        }
    }

 

 

The actual view is simply a user control which contains instances of the digits inside and arranges them accordingly.  The class which I have not mentioned is the separator class and that is simply two ellipses which I have added control for, both for size and the colour inside the containers.

<UserControl x:Class="DigitalClock.DigitalClockView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:custom="clr-namespace:DigitalClock">
    <Grid x:Name="LayoutRoot" Background="Black">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
        <custom:DigitalDigit x:Name="Hour1" Grid.Column="0" PointSize="20" OnColor="Beige" />
        <custom:DigitalDigit x:Name="Hour2" Grid.Column="1" PointSize="20" OnColor="Beige"/>
        <custom:DigitalClockSeparator x:Name="sep1" Grid.Column="2" HorizontalAlignment="Center" />
        <custom:DigitalDigit x:Name="Minute1" Grid.Column="3" PointSize="20" />
        <custom:DigitalDigit x:Name="Minute2" Grid.Column="4" PointSize="20" />
        <custom:DigitalClockSeparator x:Name="sep2" Grid.Column="5" HorizontalAlignment="Center"/>
        <custom:DigitalDigit x:Name="Second1" Grid.Column="6" PointSize="20" />
        <custom:DigitalDigit x:Name="Second2" Grid.Column="7" PointSize="20" />
    </Grid>
</UserControl>

I found a couple of gotchas and a big one includes that upon an exception, SilverLight will continue to try and render what it has even after an exception and it looks likes values default to zero, which admittedly seems logical.  Take a peek at this screen print from the above app during development.

image 

Tis all for now,

Cheers,


Andrew

Published Monday, December 14, 2009 12:15 AM by REA_ANDREW
Filed under: ,

Comments

# Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; | Design Graphics

Pingback from  Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; | Design Graphics

# Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; Silverlight Web

Pingback from  Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; Silverlight Web

# Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; iSilverlight

Pingback from  Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; iSilverlight

# Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; Silverlight Blog

Pingback from  Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; Silverlight Blog

# Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; Zero Me

Pingback from  Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; Zero Me

# Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; Private Me

Pingback from  Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; Private Me

# Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; &laquo; Blogging

Pingback from  Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; &laquo;  Blogging

# Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; &laquo; Blogging

Pingback from  Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock &#8230; &laquo;  Blogging

# Twitter Trackbacks for Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock - Andrew Rea [asp.net] on Topsy.com

Pingback from  Twitter Trackbacks for                 Programmatic Drawing with Silverlight 2.0/3.0 ??? A Digital Clock - Andrew Rea         [asp.net]        on Topsy.com

# Links (12/17/2009) &laquo; Steve Pietrek-Everything SharePoint/Silverlight

Pingback from  Links (12/17/2009) &laquo; Steve Pietrek-Everything SharePoint/Silverlight

Leave a Comment

(required) 
(required) 
(optional)
(required)