[C# Helper]
Index Books FAQ Contact About Rod
[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[C# 24-Hour Trainer]

[C# 5.0 Programmer's Reference]

[MCSD Certification Toolkit (Exam 70-483): Programming in C#]

Title: Make a digital clock in C#

[Make a digital clock in C#]

This example uses the techniques described in the example Draw LED-style letters in C# to draw a digital clock. That earlier example showed one way to draw LED-style letters. This example simply uses the LedText class from that post to draw the digital clock.

Drawing Colons

If you look at a real digital clock, you'll see that it has several special-purpose LEDs. Most digital clocks use special LEDs to draw the colon between the hours, minutes, and seconds. Those positions are always colons, so they don't need to hold a bunch of LEDs that you can turn on and off. Instead they simply hold two LEDs that are always bright. Some clocks also have periods, slashes, or other LEDs that never change. For this example I decided to only worry about the colons.

Because the colon (and other punctuation symbols) don't need to use a bunch of LEDs, they are relatively thin. You could make a more flexible model of LED letters to include these special characters, but I decided to just make a method to draw LEDs.

The new example adds the following DrawColon method to the LedText class.

// Draw a colon. public void DrawColon(Graphics gr, Brush bg_brush, Brush used_brush, Pen used_pen, Brush unused_brush, Pen unused_pen, PointF position) { // Clear the background. gr.FillRectangle(bg_brush, position.X, position.Y, LedThickness, CellHeight); float y1 = position.Y + CellHeight / 4f; float y2 = y1 + CellHeight / 2f; RectangleF rect1 = new RectangleF( position.X, y1 - LedThickness / 2f, LedThickness, LedThickness); gr.FillRectangle(used_brush, rect1); gr.DrawRectangle(used_pen, rect1); RectangleF rect2 = new RectangleF( position.X, y2 - LedThickness / 2f, LedThickness, LedThickness); gr.FillRectangle(used_brush, rect2); gr.DrawRectangle(used_pen, rect2); }

This method simply draws two rectangles that are as thick as the object's LedThickness value and positioned 1/4 and 3/4 of the way down from the top of the letter's cell.

Note that the Graphics class has a FillRectangle method that fills a RectangleF, but it does not have a corresponding DrawRectangle method to draw a RectangleF. To make drawing the rectangles easier, I created the following extension method.

public static class Extensions { public static void DrawRectangle(this Graphics gr, Pen pen, RectangleF rect) { gr.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height); } }

The LedText class needs only one other change to handle the colon. That change is shown in the following DrawText method.

// Draw a sequence of letters. public void DrawText(Graphics gr, Brush bg_brush, Brush used_brush, Pen used_pen, Brush unused_brush, Pen unused_pen, PointF position, float h_spacing, string text) { float cell_space = CellWidth * (h_spacing - 1); foreach (char ch in text) { if (ch == ':') { DrawColon(gr, bg_brush, used_brush, used_pen, unused_brush, unused_pen, position); position.X += LedThickness; } else { DrawLetter(gr, bg_brush, used_brush, used_pen, unused_brush, unused_pen, position, ch); position.X += CellWidth; } position.X += cell_space; } }

This method first calculates the amount of horizontal space it should add between letters. The h_spacing value indicates the amount by which the cell width should be multiplied to move to the next letter. For example, this value might be 1.2. The method finds the amount of space between cells by multiplying this value by the cell width and then subtracting the cell width.

The code then loops through the text's letters as before. This time if the letter is a colon, the method calls the DrawColon method and adds the width of the colon, which is LedThickness. If the letter is not a color, then the method calls the DrawLetter method as before and adds the letter's width, which is CellWidth. In either case the code then adds the space between letters cell_space.

Drawing the Clock

When the program starts, the following code prepares the LedText objects that the program uses to draw the clock.

private LedText TimeLedText, DateLedText; private void Form1_Load(object sender, EventArgs e) { const float cell_width = 50; const float cell_height = 80; const float led_thickness = 7; const float gap = 1.5f; TimeLedText = new LedText(cell_width, cell_height, led_thickness, gap); const float scale = 0.95f; DateLedText = new LedText(scale * cell_width, scale * cell_height, scale * led_thickness, scale * gap); }

This code declares the TimeLedText and DateLedText objects at the form level. The form's Load event handler defines some constants and creates the objects. It scales the DateLedText object's parameters by 0.95 (a number I found by trial and error) to make the date text as wide as the time text.

The program's form contains a timer that fires every 500 milliseconds. When it does, the following code executes.

private void tmrTick_Tick(object sender, EventArgs e) { picClock.Refresh(); } private void picCanvas_Paint(object sender, PaintEventArgs e) { ShowTime(e.Graphics); } // Display the time. private void ShowTime(Graphics gr) { gr.Clear(Color.Black); gr.SmoothingMode = SmoothingMode.AntiAlias; const float margin = 5f; PointF position = new PointF(margin, margin); using (Brush unused_brush = new SolidBrush(Color.FromArgb(0, 40, 0))) { // Draw the time. Brush bg_brush = Brushes.Black; Brush used_brush = Brushes.Lime; Pen used_pen = Pens.Transparent; Pen unused_pen = Pens.Transparent; TimeLedText.DrawText(gr, bg_brush, used_brush, used_pen, unused_brush, unused_pen, position, 1.2f, DateTime.Now.ToLongTimeString()); } using (Brush unused_brush = new SolidBrush(Color.FromArgb(0, 0, 60))) { // Draw the time. Brush bg_brush = Brushes.Black; Brush used_brush = Brushes.Blue; Pen used_pen = Pens.Transparent; Pen unused_pen = Pens.Transparent; position.Y += TimeLedText.CellHeight + 4 * TimeLedText.LedThickness; // Draw the day and date. string date_string = DateTime.Now.DayOfWeek.ToString(); date_string = date_string.ToUpper().Substring(0, 3); date_string += " " + DateTime.Now.Day.ToString() + "/" + DateTime.Now.Year.ToString().Substring(0, 2); DateLedText.DrawText(gr, bg_brush, used_brush, used_pen, unused_brush, unused_pen, position, 1.2f, date_string); } }

The Timer's Tick event handler refreshes the picClock PictureBox. That control's Paint event handler calls the ShowTime method to do all of the interesting work.

The ShowTime method is relatively straightforward. It defines some brushes and pens and then passes them to the TimeLedText object's DrawText method to draw the time. It then repeats those steps to draw the date with different brushes and pens. The only really interesting step is where the code adds space to the Y coordinate of the position where it draws the text so the date is drawn far enough below the time.

Conclusion

This is a relatively simple example. It draws the time and date text at specific positions and sizes to make everything fit nicely. You could modify it to calculate the text sizes at runtime to fit the form, but seemed like more work than it was worth for such a basic example. Feel free to make that change it if you like.

The only other non-obvious detail is that I set the form's FormBorderStyle property to FixedToolWindow at design time.

Download the example to experiment with it and to see additional details.

© 2009-2023 Rocky Mountain Computer Consulting, Inc. All rights reserved.