/* 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 ); } /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> 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