You should give your controls, properties, fields and methods descriptive names instead of using the default names given by the designer - for instance:
timer1
could be WatchTimer
label1
could be NumericDisplay
etc.
You should split the paint event handler into sub methods handling each part of the display:
public void MainForm_Paint(object sender, PaintEventArgs e)
{
DrawDial(e);
DrawHands(e);
}
You can optimize different things:
The pens are never changed, so they are candidates for instance fields instead of being recreated each time they are used.
public partial class MainForm : Form
{
Pen _secHandPen = new Pen(Color.Green, 1);
Pen _minHandPen = new Pen(Color.Black, 2);
Pen _hourHandPen = new Pen(Color.Red, 3);
...
You then have to remember to dispose of them when disposing the form (Dispose()
can befound in "Form.Designer.cs"):
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
components.Dispose();
DisposePen(ref _secHandPen);
DisposePen(ref _minHandPen);
DisposePen(ref _hourHandPen);
}
base.Dispose(disposing);
}
private void DisposePen(ref Pen pen)
{
if (pen != null)
{
pen.Dispose();
pen = null;
}
}
The dimensions and center of the hands and the dial only change when the size of the form changes, so create the needed class fields and handle changes in the Form_SizedChanged
event handler:
private void MainForm_SizeChanged(object sender, EventArgs e)
{
SetDimensions();
Refresh();
}
private void SetDimensions()
{
_center = new PointF(ClientSize.Width / 2, ClientSize.Height / 2);
_watchDiameter = (int)((ClientSize.Height - NumericDisplay.Height < ClientSize.Width ? ClientSize.Height - NumericDisplay.Height : ClientSize.Width) * 0.9);
}
Here _center
and _watchDiameter
are defined as class fields:
DateTime _clockTime = DateTime.Now;
PointF _center;
int _watchDiameter = 200;
And NumericDisplay (label1)
is docked to the bottom of the form, so when calculating the optimal watch size its height must be considered.
All in all a refactor of your form including the above and some of VisualMelons suggestions could be:
public partial class MainForm : Form
{
DateTime _clockTime = DateTime.Now;
PointF _center;
int _watchDiameter = 200;
Pen _secHandPen = new Pen(Color.Green, 1);
Pen _minHandPen = new Pen(Color.Black, 2);
Pen _hourHandPen = new Pen(Color.Red, 3);
public MainForm()
{
InitializeComponent();
SetDimensions();
WatchTimer.Enabled = true;
WatchTimer.Interval = 500;
WatchTimer.Start();
}
private void WatchTimer_Tick(object sender, EventArgs e)
{
_clockTime = DateTime.Now;
NumericDisplay.Text = _clockTime.ToLongTimeString(); // String.Format("{0:00}:{1:00}:{2:00}", _clockTime.Hour, _clockTime.Minute, _clockTime.Second);
Refresh();
}
void DrawDial(PaintEventArgs e)
{
e.Graphics.DrawEllipse(Pens.DarkBlue, _center.X - _watchDiameter / 2, _center.Y - _watchDiameter / 2, _watchDiameter, _watchDiameter);
}
void DrawHands(PaintEventArgs e)
{
DrawHand(e, _hourHandPen, (int)(_watchDiameter * 0.3), _clockTime.Hour, 12);
DrawHand(e, _minHandPen, (int)(_watchDiameter * 0.45), _clockTime.Minute, 60);
DrawHand(e, _secHandPen, (int)(_watchDiameter * 0.45), _clockTime.Second, 60);
}
void DrawHand(PaintEventArgs e, Pen pen, int offset, int timeValue, int denom)
{
PointF end = GetEnd(offset, timeValue, denom);
e.Graphics.DrawLine(pen, _center, end);
}
PointF GetEnd(int offset, int timeValue, int denom)
{
double angle = Math.PI / -2 + (2 * timeValue * Math.PI) / denom;
return new PointF(
offset * (float)Math.Cos(angle) + _center.X,
offset * (float)Math.Sin(angle) + _center.Y);
}
public void MainForm_Paint(object sender, PaintEventArgs e)
{
DrawDial(e);
DrawHands(e);
}
private void MainForm_SizeChanged(object sender, EventArgs e)
{
SetDimensions();
Refresh();
}
private void SetDimensions()
{
_center = new PointF(ClientSize.Width / 2, ClientSize.Height / 2);
_watchDiameter = (int)((ClientSize.Height - NumericDisplay.Height < ClientSize.Width ? ClientSize.Height - NumericDisplay.Height : ClientSize.Width) * 0.9);
}
}
For completeness here is the designer code with the updated names and event handlers etc.:
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
components.Dispose();
DisposePen(ref _secHandPen);
DisposePen(ref _minHandPen);
DisposePen(ref _hourHandPen);
}
base.Dispose(disposing);
}
private void DisposePen(ref Pen pen)
{
if (pen != null)
{
pen.Dispose();
pen = null;
}
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.WatchTimer = new System.Windows.Forms.Timer(this.components);
this.NumericDisplay = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// WatchTimer
//
this.WatchTimer.Tick += new System.EventHandler(this.WatchTimer_Tick);
//
// NumericDisplay
//
this.NumericDisplay.Dock = System.Windows.Forms.DockStyle.Bottom;
this.NumericDisplay.Location = new System.Drawing.Point(0, 437);
this.NumericDisplay.Name = "NumericDisplay";
this.NumericDisplay.Size = new System.Drawing.Size(800, 13);
this.NumericDisplay.TabIndex = 0;
this.NumericDisplay.Text = "09:41:37";
this.NumericDisplay.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// TheForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.NumericDisplay);
this.DoubleBuffered = true;
this.Name = "MainForm";
this.Text = "Watch";
this.SizeChanged += new System.EventHandler(this.MainForm_SizeChanged);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Timer WatchTimer;
private System.Windows.Forms.Label NumericDisplay;
}
There are still a couple of "magic numbers" in the code:
void DrawHands(PaintEventArgs e)
{
DrawHand(e, _hourHandPen, (int)(_watchDiameter * 0.3), _clockTime.Hour, 12);
DrawHand(e, _minHandPen, (int)(_watchDiameter * 0.45), _clockTime.Minute, 60);
DrawHand(e, _secHandPen, (int)(_watchDiameter * 0.45), _clockTime.Second, 60);
}
To get rid of them you could create a WatchHand
type to hold those with descriptive names.
Pen
andGraphics
implementIDisposable
and therefore all four declarations should be wrapped inusing
constructs. \$\endgroup\$