C# 中的模拟时钟控件






4.40/5 (39投票s)
如何在 C# 中制作一个模拟时钟控件。
引言
本文展示了如何使用简单高效的代码在 C# 中创建一个模拟时钟控件,并且该控件能够根据调整后的新尺寸自动调整。有一些设置可以使控件更具可定制性。作为一个组件,我们可以将我们的控件添加到工具箱,将其拖放到窗体上,并以任何尺寸绘制它,它看起来都会很棒。
谁应该阅读本文
如果您是 C# 控件的新手,但对 C# 及其语法非常熟悉,那么本文适合您。它还假定您具备一些高中数学的基础知识。
背景
要开始制作我们的时钟,我们需要一些数学知识。时钟指针的位置以极坐标系指定。分钟/小时/秒值只是指针角度位移的函数。因此,我们必须编写一些代码将这个极坐标量转换为直角坐标量,因为 C# 图形不支持极坐标系;我们必须指定指针的 x 和 y 坐标。
我们首先需要找到当前时间的值。
DateTime dateTime = DateTime.Now;
int hour = dateTime.Hour % 12; // also converts 24hrs to 12hrs format
int minute = dateTime.Minute;
int sec = dateTime.Second;
我们需要将这些值转换为弧度。在一根指针的完整旋转中,有 360 度(弧度),但只有 12 小时;所以要将小时转换为弧度,我们必须编写
float hourRadian = hour * 360/12 * PI/180;
类似地,由于一圈有 60 分钟(和秒),我们可以得到以下公式:
float minRadian = minute * 360/60 * PI/180;
float secRadian = sec * 360/60 * PI/180;
现在只剩下将这些极坐标量转换为直角坐标量,以便我们可以使用 x 和 y 坐标绘制一条线(指针)。让我们回顾一下我们的学校数学公式来解决这个问题。
X = r*cos(angle)
Y = r*sin(angle)
但是这里有一个问题:时钟是顺时针旋转的,而我们的极坐标系统是逆时针旋转的;所以如果我们直接使用上面的公式,输出将是时钟的“镜像”(即,逆时针旋转)。为了解决这个问题,让我们在上面的公式中反转正弦和余弦的位置;因此,我们将用于将时钟指针的位置(其终点)从极坐标转换为直角坐标的代码将是:
float hourEndPointX = lengthofHourHand * System.Math.Sin(hourRadian)
float hourEndPointY = lengthofHourHand * System.Math.Cos(hourRadian)
现在我们已经完成了所有转换,是时候从时钟中心绘制一条线到指针的端点了。我们可以这样做:
Line(centerX, centerY, hourEndPointX, hourEndPointY)
上面的代码可以正常工作,但从某种意义上说,它更像是数字时钟而不是模拟时钟,因为小时指针仅在一个小时后更新。例如,当时间从 9:59 变为 10:00 时,观察您的时钟,小时指针直接从 9 移动到 10。我们需要做一些事情,以便小时指针每分钟也更新其位置。为此,让我们在计算小时弧度的过程中包含分钟的比例,如下所示:
float hour = dateTime.Hour%12 + (float)dateTime.Minute/60
float hourRadian = hour * 360/12 * PI/180;
现在将这些极坐标量转换为直角坐标形式并绘制指针。
float hourEndPointX = lengthofHourHand * System.Math.Sin(hourRadian);
float hourEndPointY = lengthofHourHand * System.Math.Cos(hourRadian);
Line(centerX, centerY, hourEndPointX, hourEndPointY);
以类似的方式,我们可以编写代码来绘制分钟和秒针。
创建我们的控件
现在我们已经了解了时钟实现的基础知识,是时候构建我们的控件了。创建一个名为“AnalogClockControl”的新项目。确保从模板中选择“Windows 控件库”。
从项目中删除“usercontrol1.cs”,然后向项目中添加一个新类。
从模板中选择“用户控件”,将“UserControl1.cs”重命名为“AnalogClock.cs”。
打开设计视图,将一个计时器控件从工具箱拖放到您的控件上。将其 Interval 属性设置为 1000,将 Enabled 属性设置为 true。此计时器将用于使我们的时钟“运行”,即它将每秒更新我们的时钟。
让我们通过声明以下成员变量开始编码:
public class AnalogClock : System.Windows.Forms.UserControl
{
const float PI=3.141592654F;
DateTime dateTime;
float fRadius;
float fCenterX;
float fCenterY;
float fCenterCircleRadius;
float fHourLength;
float fMinLength;
float fSecLength;
float fHourThickness;
float fMinThickness;
float fSecThickness;
bool bDraw5MinuteTicks=true;
bool bDraw1MinuteTicks=true;
float fTicksThickness=1;
Color hrColor=Color.DarkMagenta ;
Color minColor=Color.Green ;
Color secColor=Color.Red ;
Color circleColor=Color.Red;
Color ticksColor=Color.Black;
...
...
每当我们的控件加载时,都必须初始化其属性。我们的时钟也应该在调整为新值时进行调整。因此,将以下代码添加到我们控件的 Load
和 Resize
事件中。在 Resize
事件中,我指定了(硬编码了)某些值以获得良好的外观,如果您愿意,可以更改这些值。
private void AnalogClock_Load(object sender, System.EventArgs e)
{
dateTime=DateTime.Now;
this.AnalogClock_Resize(sender,e);
}
private void AnalogClock_Resize(object sender, System.EventArgs e)
{
this.Width = this.Height;
this.fRadius = this.Height/2;
this.fCenterX = this.ClientSize.Width/2;
this.fCenterY = this.ClientSize.Height/2;
this.fHourLength = (float)this.Height/3/1.65F;
this.fMinLength = (float)this.Height/3/1.20F;
this.fSecLength = (float)this.Height/3/1.15F;
this.fHourThickness = (float)this.Height/100;
this.fMinThickness = (float)this.Height/150;
this.fSecThickness = (float)this.Height/200;
this.fCenterCircleRadius = this.Height/50;
this.Refresh();
}
每当我们的计时器的 Tick
事件被触发时,我们的 dateTime
变量应该立即更新,并且时钟必须重绘。双击 timer1
以进入 timer1_Tick
事件并将以下代码添加到其中:
private void timer1_Tick(object sender, System.EventArgs e)
{
this.dateTime=DateTime.Now;
this.Refresh();
}
让我们编写两个函数,DrawLine()
和 DrawPolygon()
,以绘制指定长度、厚度和颜色的时钟指针,指向一个位置由弧度给出的点。
private void DrawLine(float fThickness, float fLength, Color color,
float fRadians, System.Windows.Forms.PaintEventArgs e)
{
e.Graphics.DrawLine(new Pen( color, fThickness ),
fCenterX - (float)(fLength/9*System.Math.Sin(fRadians)),
fCenterY + (float)(fLength/9*System.Math.Cos(fRadians)),
fCenterX + (float)(fLength*System.Math.Sin(fRadians)),
fCenterY - (float)(fLength*System.Math.Cos(fRadians)));
}
private void DrawPolygon(float fThickness, float fLength, Color color,
float fRadians, System.Windows.Forms.PaintEventArgs e)
{
PointF A=new PointF( (float)(fCenterX+
fThickness*2*System.Math.Sin(fRadians+PI/2)),
(float)(fCenterY -
fThickness*2*System.Math.Cos(fRadians+PI/2)) );
PointF B=new PointF( (float)(fCenterX+
fThickness*2*System.Math.Sin(fRadians-PI/2)),
(float)(fCenterY -
fThickness*2*System.Math.Cos(fRadians-PI/2)) );
PointF C=new PointF( (float)(fCenterX+
fLength*System.Math.Sin(fRadians)),
(float) (fCenterY -
fLength*System.Math.Cos(fRadians)) );
PointF D=new PointF( (float)(fCenterX-
fThickness*4*System.Math.Sin(fRadians)),
(float)(fCenterY +
fThickness*4*System.Math.Cos(fRadians) ));
PointF[] points={A,D,B,C};
e.Graphics.FillPolygon( new SolidBrush(color), points );
}
现在我们添加另外两个函数,Start()
和 Stop()
:
public void Start()
{
timer1.Enabled=true;
this.Refresh();
}
public void Stop()
{
timer1.Enabled=false;
}
我们控件的 Paint
事件实际上将绘制我们的时钟,所以我们必须将以下代码写入我们控件的 Paint
事件。它首先将小时、分钟和秒的值转换为弧度,然后调用 DrawPolygon()
和 DrawLine()
方法来绘制相应的指针。它还为我们的时钟绘制刻度。
private void AnalogClock_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
float fRadHr=(dateTime.Hour%12+dateTime.Minute/60F) *30*PI/180;
float fRadMin=(dateTime.Minute)*6*PI/180;
float fRadSec=(dateTime.Second)*6*PI/180;
DrawPolygon(this.fHourThickness,
this.fHourLength, hrColor, fRadHr, e);
DrawPolygon(this.fMinThickness,
this.fMinLength, minColor, fRadMin, e);
DrawLine(this.fSecThickness,
this.fSecLength, secColor, fRadSec, e);
for(int i=0;i<60;i++)
{
if ( this.bDraw5MinuteTicks==true && i%5==0 )
// Draw 5 minute ticks
{
e.Graphics.DrawLine( new Pen( ticksColor, fTicksThickness ),
fCenterX +
(float)( this.fRadius/1.50F*System.Math.Sin(i*6*PI/180) ),
fCenterY -
(float)( this.fRadius/1.50F*System.Math.Cos(i*6*PI/180) ),
fCenterX +
(float)( this.fRadius/1.65F*System.Math.Sin(i*6*PI/180) ),
fCenterY -
(float)( this.fRadius/1.65F*System.Math.Cos(i*6*PI/180)) );
}
else if ( this.bDraw1MinuteTicks==true ) // draw 1 minute ticks
{
e.Graphics.DrawLine( new Pen( ticksColor, fTicksThickness ),
fCenterX +
(float) ( this.fRadius/1.50F*System.Math.Sin(i*6*PI/180) ),
fCenterY -
(float) ( this.fRadius/1.50F*System.Math.Cos(i*6*PI/180) ),
fCenterX +
(float) ( this.fRadius/1.55F*System.Math.Sin(i*6*PI/180) ),
fCenterY -
(float) ( this.fRadius/1.55F*System.Math.Cos(i*6*PI/180) ) );
}
}
//draw circle at center
e.Graphics.FillEllipse( new SolidBrush( circleColor ),
fCenterX-fCenterCircleRadius/2,
fCenterY-fCenterCircleRadius/2,
fCenterCircleRadius, fCenterCircleRadius);
}
我们的时钟控件已完成,现在是时候添加一些属性到我们的控件,以便用户可以更改其外观。
public Color HourHandColor
{
get { return this.hrColor; }
set { this.hrColor=value; }
}
public Color MinuteHandColor
{
get { return this.minColor; }
set { this.minColor=value; }
}
public Color SecondHandColor
{
get { return this.secColor; }
set { this.secColor=value;
this.circleColor=value; }
}
public Color TicksColor
{
get { return this.ticksColor; }
set { this.ticksColor=value; }
}
public bool Draw1MinuteTicks
{
get { return this.bDraw1MinuteTicks; }
set { this.bDraw1MinuteTicks=value; }
}
public bool Draw5MinuteTicks
{
get { return this.bDraw5MinuteTicks; }
set { this.bDraw5MinuteTicks=value; }
}
一切就绪。通过构建解决方案来编译控件。它将创建“AnalogClock.dll”。这是我们稍后在其他项目中需要使用此控件时使用的文件。
使用控件
成功构建解决方案后,我们可以在其他项目中轻松使用我们的控件。方法如下:从工具箱弹出菜单中选择“添加/删除项..”,然后使用“浏览”按钮找到“AnalogClock.dll”。一旦控件被添加到工具箱,我们就可以轻松地将其拖放到窗体上,并像其他常规控件一样使用它。享受吧。
历史
- 版本 1.0。
- 初始版本。需要您的改进建议。