The code demonstrates the simplicity of using GDI+ for simple drawing tasks. I have used it to make a simple desktop clock. In this little clock, users have the ability to choose the clock image that they want (which is just an image file); I have also added this little feature that each clock image can be associated with an optional .ini file that has the settings of that clock image. These settings include: the color and size of each clock hand, the centre of the clock (in case that the image is not symmetric), showing date, etc.
I have actually put some clock images and their .ini files in the project folder to demonstrate the features of this program.
The Code Structure
There is the
AceClockForm class which is the only form used in the program, all the calculations and drawings using GDI+ are done in this class. The
INISettingsReaderWriter class is used to store and retrieve settings for the program, and to retrieve the settings of clocks. Finally, the
ClockSettings classes store the settings of the program and clocks.
Drawing the Clock
To draw the clock hands, which are simply lines in my application, we need to know their coordinates! For each line, one of the two points of the line is simply the centre of the clock; for the other point, the angle of that line (or clock hand) is calculated first:
double alpha = now.Hour * (360/12) + now.Minute / 2;
double beta = now.Minute * (360/60) + now.Second / 10;
double gama = (now.Second+(drawSHContinuously? now.Millisecond/1000f : 0f)) * 360/60;
Here is the explanation:
The hour hand: the circle of a clock face is divided by 12 hours, so each hour is 1/12 of the circle, and since a circle is 360 degrees, each hour is 360/12 degrees, so the angle of the hour hand is calculated this way, but as you all have noticed, no analog clock's hour hand stands still exactly on the current hour number until an hour is passed. Instead, it moves slowly towards the next number. In order to calculate this angle, the minute part of the time is taken into account. The calculation is almost the same except that since each 60 minutes equal an hour, a division by 60 is added.
The minute hand: It is like the hour hand, the difference is that each minute is 1/60 of the circle, so it is 360/60 degrees, and again, to make it look smoother, the seconds are taken into account, and since each 60 seconds is 1 minute, a division by 60 is added.
The second hand: again, each second is 1/60 of the circle, etc... but there is this
drawSHContinuously thing. In my application, there is an option that allows the second hand to be drawn continually. If that option is activated, then the milliseconds become significant because the second hand is redrawn many times between each 2 seconds.
So far the angles of each hand are calculated, but these angles are in degrees, they are clockwise (!) (as opposed to trigonometry), and they are relative to the vertical line, so they are converted to CCW, radiant, relative to the horizontal line angles:
alpha = -alpha * Math.PI / 180d + Math.PI / 2d;
beta = -beta * Math.PI / 180d + Math.PI / 2d;
gama = -gama * Math.PI / 180d + Math.PI / 2d;
Then these angles are used to calculate the coordinates of each hand's end points. This is done in the
getTaleCoordinates() method, which gets the coordinates of one end point of a line, its length and angle, and calculates its other end point.
Using GDI+ to Draw the Clock
I have used
BufferedGraphics in the code to eliminate possible blinks of the clock image. This
BufferedGraphics is allocated in the
SetImage() method in the code. The
BufferedGraphics class has a
Graphics property which returns the
Graphics object on which the clock is drawn.
The actual drawing is done in the
drawClock() method. The following line of code draws the clock image:
bgg.DrawImage(this.image, 0, 0, this.Width, this.Height);
and these lines draw the clock hands:
clockSettings.MinuteTale, beta + Math.PI),
clockSettings.SecondTale, gama + Math.PI));
The first parameter to the
DrawLine method is a
Pen, and the second and the third are the ending points of the line to be drawn.
The date is also drawn. The
DrawString method is used to draw strings in GDI+. The use of this method is simple, it takes the string to be drawn, the font by which the string is to be drawn, a brush which can be used to define a texture for the text, and the coordinates of the upper left corner of the string. Here is the code:
bgg.DrawString(now.ToShortDateString(), dateFont, dateBrush,
clockSettings.DateCenterX - strsize.Width / 2,
clockSettings.DateCenterY - strsize.Height / 2);
Settings and ClockSettings Classes
Settings class stores the settings of the program, and the
ClcokSettings class stores the settings of a clock. Loading and saving the data of these classes is explained below.
Loading and Saving Clocks and Programs Settings
INISettingsReaderWriter class is used to save and load settings for both the clocks and the application itself. As the name suggests, these settings are stored in the form of INI files, in which each piece of data is stored in a separate line in this form:
Key=Value ;this is a comment!
This class uses Regular Expressions to parse data stored in INI files, and uses Reflection to fill objects with these data, or write them to a file. Here is the Regular Expression pattern that is used:
static Regex iniFilePattern =
new Regex(@"^\s*(?<key>\w+)\s*=[\s-[\n]]*(?<value>" +
RegexOptions.Multiline | RegexOptions.Compiled);
objcetToDictionary are provided to store the data in a
System.Collections.Generic.Dictionary<string, string> object in the properties of the given object, and vice versa. As I have mentioned above, this is done through Reflection:
foreach (var pi in t.GetProperties())