2D Graphics C# Tutorial

/* Quote from 
Programming .NET Windows Applications
By Jesse Liberty, Dan Hurwitz
First Edition October 2003 
Pages: 1246 (More details)
*/
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Timers;
using System.Windows.Forms;
namespace Clock3CS
{
  // Rename the class
  public class ClockFace : System.Windows.Forms.Form
  {
    // Required designer variable.
    private System.ComponentModel.Container components = null;
  
    private int FaceRadius = 450;    // size of the clock face
    private bool b24Hours = false;    // 24 hour clock face?
    private System.Windows.Forms.Button btnClockFormat;    
    private DateTime currentTime;    // used in more than one method
    // new
    private int xCenter;        // center of the clock
    private int yCenter;
    private static int DateRadius = 600; // outer circumference for date
    private static int Offset = 0;    // for moving the text 
    Font font = new Font("Arial", 40);  // use the same font throughout
    private StringDraw sdToday;      // the text to animate
    public ClockFace()
    {
      // Required for Windows Form Designer support
      InitializeComponent();
      // use the user's choice of colors
      BackColor = SystemColors.Window;
      ForeColor = SystemColors.WindowText;
    
      // *** begin new
      string today = System.DateTime.Now.ToLongDateString();
      today = " " + today.Replace(",","");
      
      // create a new stringdraw object with today's date
      sdToday = new StringDraw(today,this);
      currentTime = DateTime.Now;
      // set the current center based on the
      // client area
      xCenter = Width / 2;
      yCenter = Height / 2;
      // *** end new
      // update the clock by timer
      System.Timers.Timer timer = new System.Timers.Timer();
      timer.Elapsed += new System.Timers.ElapsedEventHandler(OnTimer);
      timer.Interval = 20;  // shorter interval - more movement
      timer.Enabled = true;
    }
    protected override void OnPaint ( PaintEventArgs e )
    {
      base.OnPaint(e);
      Graphics g = e.Graphics;
      SetScale(g);
      DrawFace(g);
      DrawTime(g,true);  // force an update
    }
    // every time the timer event fires, update the clock
    public void OnTimer(Object source, ElapsedEventArgs e)
    {
      Graphics g = this.CreateGraphics();
      SetScale(g);
      DrawFace(g);
      DrawTime(g,false);
      DrawDate(g);
      g.Dispose();    
      
    }
    #region Windows Form Designer generated code
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if (components != null) 
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }
    
    /// 
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// 

    private void InitializeComponent()
    {
      this.btnClockFormat = new System.Windows.Forms.Button();
      this.SuspendLayout();
      // 
      // btnClockFormat
      // 
      this.btnClockFormat.Location = new System.Drawing.Point(8, 8);
      this.btnClockFormat.Name = "btnClockFormat";
      this.btnClockFormat.TabIndex = 1;
      this.btnClockFormat.Text = "24 Hours";
      this.btnClockFormat.Click += new System.EventHandler(this.btnClockFormat_Click);
      // 
      // ClockFace
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(292, 266);
      this.Controls.AddRange(new System.Windows.Forms.Control[] {
                                      this.btnClockFormat});
      this.Name = "ClockFace";
      this.Text = "Clock3CS";
      this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.ClockFace_MouseDown);
      this.ResumeLayout(false);
    }
    #endregion
    [STAThread]
    static void Main() 
    {
      Application.Run(new ClockFace());
    }
    private void SetScale(Graphics g)
    {
      // if the form is too small, do nothing
      if ( Width == 0 || Height == 0 )
        return;
      // set the origin at the center
      g.TranslateTransform(xCenter, yCenter);  // use the members vars
      // set inches to the minimum of the width 
      // or height dividedby the dots per inch  
      float inches = Math.Min(Width / g.DpiX, Height / g.DpiX);
      // set the scale to a grid of 2000 by 2000 units
      g.ScaleTransform(
        inches * g.DpiX / 2000, inches * g.DpiY / 2000);
    }
    private void DrawFace(Graphics g)
    {
      // numbers are in forecolor except flash number in green
      // as the seconds go by.
      Brush brush = new SolidBrush(ForeColor);
      float x, y;
      // new code
      int numHours = b24Hours ? 24 : 12;
      int deg = 360 / numHours;
      
      // for each of the hours on the clock face
      for (int i = 1; i <= numHours; i++)
      {
        // i = hour  30 degrees = offset per hour  
        // +90 to make 12 straight up
        x = GetCos(i*deg + 90) * FaceRadius;
        y = GetSin(i*deg + 90) * FaceRadius;
        StringFormat format = new StringFormat();
        format.Alignment = StringAlignment.Center;
        format.LineAlignment = StringAlignment.Center;
        g.DrawString(
          i.ToString(), font, brush, -x, -y,format);
      
      }  // end for loop
    }    // end drawFace
    private void DrawTime(Graphics g, bool forceDraw)
    {
      //  length of the hands
      float hourLength = FaceRadius * 0.5f;
      float minuteLength = FaceRadius * 0.7f;
      float secondLength = FaceRadius * 0.9f;
      // set to back color to erase old hands first
      Pen hourPen = new Pen(BackColor);
      Pen minutePen = new Pen(BackColor);
      Pen secondPen = new Pen(BackColor);
      // set the arrow heads
      hourPen.EndCap = LineCap.ArrowAnchor;
      minutePen.EndCap = LineCap.ArrowAnchor;
      // hour hand is thicker
      hourPen.Width = 30;
      minutePen.Width = 20;
      // second hand 
      Brush secondBrush = new SolidBrush(BackColor);
      const int EllipseSize = 50;
      GraphicsState state;  // to to protect and to serve
      // 1 - delete the old time
      // delete the old second hand
      // figure out how far around to rotate to draw the second hand
      // save the current state, rotate, draw and then restore the state
      float rotation = GetSecondRotation();
      state = g.Save();
      g.RotateTransform(rotation);
      g.FillEllipse(
        secondBrush,
        -(EllipseSize/2),
        -secondLength,
        EllipseSize,
        EllipseSize);
      g.Restore(state);
      DateTime newTime = DateTime.Now;
      bool newMin = false;  // has the minute changed?
      // if the minute has changed, set the flag
      if ( newTime.Minute != currentTime.Minute )
        newMin = true;
      // if the minute has changed or you must draw anyway then you 
      // must first delete the old minute and hour hand
      if ( newMin  || forceDraw )
      {
        // figure out how far around to rotate to draw the minute hand
        // save the current state, rotate, draw and then restore the state
        rotation = GetMinuteRotation();
        state = g.Save();
        g.RotateTransform(rotation);
        g.DrawLine(minutePen,0,0,0,-minuteLength);
        g.Restore(state);
        // figure out how far around to rotate to draw the hour hand
        // save the current state, rotate, draw and then restore the state
        rotation = GetHourRotation();
        state = g.Save();
        g.RotateTransform(rotation);
        g.DrawLine(hourPen,0,0,0,-hourLength);
        g.Restore(state);
      }
      // step 2 - draw the new time
      currentTime = newTime;
      hourPen.Color = Color.Red;
      minutePen.Color = Color.Blue;
      secondPen.Color = Color.Green;
      secondBrush = new SolidBrush(Color.Green);
      // draw the new second hand
      // figure out how far around to rotate to draw the second hand
      // save the current state, rotate, draw and then restore the state
      state = g.Save();
      rotation = GetSecondRotation();
      g.RotateTransform(rotation);
      g.FillEllipse(
        secondBrush,
        -(EllipseSize/2),
        -secondLength,
        EllipseSize,
        EllipseSize);
      g.Restore(state);
      // if the minute has changed or you must draw anyway then you 
      // must draw the new minute and hour hand
      if ( newMin || forceDraw )
      {
        // figure out how far around to rotate to draw the minute hand
        // save the current state, rotate, draw and then restore the state
        state = g.Save();
        rotation = GetMinuteRotation();
        g.RotateTransform(rotation);
        g.DrawLine(minutePen,0,0,0,-minuteLength);
        g.Restore(state);
        // figure out how far around to rotate to draw the hour hand
        // save the current state, rotate, draw and then restore the state
        state = g.Save();
        rotation = GetHourRotation();
        g.RotateTransform(rotation);
        g.DrawLine(hourPen,0,0,0,-hourLength);
        g.Restore(state);
      }
    }
    // determine the rotation to draw the hour hand
    private float GetHourRotation()
    {
      // degrees depend on 24 vs. 12 hour clock
      float deg = b24Hours ? 15 : 30;
      float numHours = b24Hours ? 24 : 12;
      return( 360f * currentTime.Hour / numHours +
        deg * currentTime.Minute / 60f);
    }
    private float GetMinuteRotation()
    {
      return( 360f * currentTime.Minute / 60f ); 
    }
    private float GetSecondRotation()
    {
      return(360f * currentTime.Second / 60f);
    }
    private static float GetSin(float degAngle)
    {
      return (float) Math.Sin(Math.PI * degAngle / 180f);
    }
    private static float GetCos(float degAngle)
    {
      return (float) Math.Cos(Math.PI * degAngle / 180f);
    }
    private void btnClockFormat_Click(object sender, System.EventArgs e)
    {
      btnClockFormat.Text = b24Hours ? "24 Hour" : "12 Hour";
      b24Hours = ! b24Hours;
      this.Invalidate();
    }
    private void DrawDate(Graphics g)
    {
      Brush brush = new SolidBrush(ForeColor);
      sdToday.DrawString(g,brush);
    }
    private void ClockFace_MouseDown(
      object sender, System.Windows.Forms.MouseEventArgs e)
    {
      xCenter = e.X;
      yCenter = e.Y;
      this.Invalidate();
    
    }
    // each letter in the outer string knows how to draw itself
    private class LtrDraw
    {
      char myChar;    // the actual letter i draw
      float x;      // current x coordinate
      float y;      // current y coordinate
      float oldx;      // old x coordinate (to delete)
      float oldy;      // old y coordinate (to delete)
      
      // constructor
      public LtrDraw(char c)
      {
        myChar = c;
      }
      // property for X coordinate
      public float X
      {
        get { return x; }
        set { oldx = x; x = value; }
      }
      // property for Y coordinate
      public float Y
      {
        get { return y; }
        set { oldy = y; y = value; }
      }
      // get total width of the string
      public float GetWidth(Graphics g, Font font)
      {
        SizeF stringSize = g.MeasureString(myChar.ToString(),font);
        return stringSize.Width;
      }
      // get total height of the string
      public float GetHeight(Graphics g, Font font)
      {
        SizeF stringSize = g.MeasureString(myChar.ToString(),font);
        return stringSize.Height;
      }
      // get the font from the control and draw the current character
      // First delete the old and then draw the new
      public void DrawString(Graphics g, Brush brush, ClockFace cf)
      {
        Font font = cf.font;
        Brush blankBrush = new SolidBrush(cf.BackColor);
        g.DrawString(myChar.ToString(),font,blankBrush,oldx,oldy);
        g.DrawString(myChar.ToString(),font,brush,x,y);
      }
    }
    // holds an array of LtrDraw objects
    // and knows how to tell them to draw
    private class StringDraw
    {
      ArrayList theString = new ArrayList();
      LtrDraw l;
      ClockFace theControl;
      // constructor takes a string, populates the array
      // and stashes away the calling control (ClockFace)
      public StringDraw(string s, ClockFace theControl)
      {
        this.theControl = theControl;
        foreach (char c in s)
        {
          l = new LtrDraw(c);
          theString.Add(l);
        }
      }
      // divide the circle by the number of letters
      // and draw each letter in position
      public void DrawString(Graphics g, Brush brush)
      {
        int angle = 360 / theString.Count;
        int counter = 0;
        foreach (LtrDraw theLtr in theString)
        {
          // 1. To find the X coordinate, take the Cosine of the angle
          // and multiply by the radius.
          // 2. To compute the angle, start with the base angle 
          // (360 divided by the number of letters)
          // and multiply by letter position.
          // Thus if each letter is 10 degrees, and this is the third
          // letter, you get 30 degrees. Add 90 to start at 12 O'clock.
          // Each time through, subtract the clockFace offset to move 
          // the entire string around the clock on each timer call
          float newX = GetCos(angle  * counter + 90 - ClockFace.Offset) * ClockFace.DateRadius ;
          float newY = GetSin(angle * counter + 90 - ClockFace.Offset) * ClockFace.DateRadius ;
          theLtr.X = 
            newX - (theLtr.GetWidth(g,theControl.font) / 2);
          theLtr.Y = 
            newY - (theLtr.GetHeight(g,theControl.font) / 2);
          counter++;
          theLtr.DrawString(g,brush,theControl);
        }
        ClockFace.Offset += 1;  // rotate the entire string
      }
    }
  }  // end class
}    // end namespace