A Simple Analog Clock Widget
Create a simple C# analog clock widget; you can do it too!

Introduction
While seeing the "Dashboard" for Mac OS and Yahoo's Konfabulator in action for so long, Microsoft finally came out with its own version of the gadget for Windows in its latest OS: Windows Vista. However, they are all in HTML and JavaScript. To test what I have learned from C# programming so far, I also came out with a C# version of the Windows gadget. I think it's quite challenging for a beginner because it requires a simple background in math, image processing and 3D graphics programming.
Using the Code
First, we need a background image for our widget, to let it have a professional look. You can set it when the form is loading. To prevent the image from being lost, I embedded the image into an application. What this code does is grab the left-top pixel and set the rest of the pixels to invisible when they meet the same color in another part of the same bitmap. Then it sets it to background and sets the form to fit the image size.
private void SetFormBackgroundImage(Bitmap bmpImage)
{
Color clrPixel = bmpImage.GetPixel(0, 0);
bmpImage.MakeTransparent(clrPixel);
this.BackgroundImage = bmpImage;
// Set the form size from image size
this.Size = bmpImage.Size;
}
To create flicker-free animation, you will need double-buffering. To achieve that:
public void EnableDoubleBuffering()
{
// Set the value of the double-buffering style bits to true.
this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint, true);
this.UpdateStyles();
}
To capture the mouse-down event, you need to store the mouse's point when it's down and offset it when the mouse moves.
private void frmIrregular_MouseDown(object sender, MouseEventArgs e)
{
ptMouseOffset = new Point(-e.X, -e.Y);
}
private void frmIrregular_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Point ptCurrMousePos = Control.MousePosition;
ptCurrMousePos.Offset(ptMouseOffset.X, ptMouseOffset.Y);
this.Location = ptCurrMousePos;
}
}
Now comes the important part of this widget: drawing the hour-hand, minute-hand and second-hand. The basic theory is...
- Set the origin of the form to center from left-top (so that the following rotation can take place)
- Save the current state
- Rotate the new graphics objects
- Draw the second-hand (or any hands) at the new origin and new orientation
- Restore the saved state
- Rotate the new graphics objects
- Draw the minute-hand (or any hands left) at the new origin and new orientation
- Reload the identity
- Reset the origin and orientation (restore the saved state if it seems failed)
- Rotate the new graphics objects
- Draw the hour-hand (or any hands left) at the new origin and new orientation
Translating these steps into codings:
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
// Set the origin to center of the form
e.Graphics.TranslateTransform(80.0F, 80.0F);
// Save translated graphics state; So origin
// will remain at center of form when restore
GraphicsState transState = e.Graphics.Save();
// Capture a copy of current time for consistent
DateTime dtNow = DateTime.Now;
// rotation starts from new center of the form
e.Graphics.RotateTransform(dtNow.Second * 6.0F - 90.0F);
// Anti-alias only affect the next shape
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
// draw the second hand at new center of the form
e.Graphics.FillRectangle(new SolidBrush(Color.Silver), -1, -1, 55, 2);
//// Restore graphics state to translated state and fill second hand
e.Graphics.Restore(transState);
// minus 90 degree because start at x-axis
e.Graphics.RotateTransform(dtNow.Minute * 6.0F - 90.0F);
// Anti-alias only affect the next shape
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillRectangle(new SolidBrush(Color.Silver), -1, -1, 45, 3);
//// Restore graphics state to translated state and fill minute hand
//gHands.Restore(transState);
// Reset transformation matrix to identity and fill rectangle.
e.Graphics.ResetTransform();
// Set the origin to center of the form
e.Graphics.TranslateTransform(80.0F, 80.0F);
// minus 90 degree because start at x-axis; Minute affects hour hand too
e.Graphics.RotateTransform(
dtNow.Hour * 30.0F - 90.0F + dtNow.Minute * 0.5F);
// Anti-alias only affect the next shape
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillRectangle(new SolidBrush(Color.Silver), -1, -1, 35, 4);
}
By forcing the form to re-paint at every second through the help of the timer: voila! I think I have presented 90% of my coding here; simple huh? You can get it from my blog too.
Version
- 2007-09-26: First release