Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Simple Analog Clock Widget

0.00/5 (No votes)
23 Oct 2007 1  
Create a simple C# analog clock widget; you can do it too!
Simple C# widget and windows gadget side-by-side.

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...

  1. Set the origin of the form to center from left-top (so that the following rotation can take place)
  2. Save the current state
  3. Rotate the new graphics objects
  4. Draw the second-hand (or any hands) at the new origin and new orientation
  5. Restore the saved state
  6. Rotate the new graphics objects
  7. Draw the minute-hand (or any hands left) at the new origin and new orientation
  8. Reload the identity
  9. Reset the origin and orientation (restore the saved state if it seems failed)
  10. Rotate the new graphics objects
  11. 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here