65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的模拟时钟控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (39投票s)

2005年2月15日

CPOL

5分钟阅读

viewsIcon

324123

downloadIcon

16337

如何在 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;

    ...
    ...

每当我们的控件加载时,都必须初始化其属性。我们的时钟也应该在调整为新值时进行调整。因此,将以下代码添加到我们控件的 LoadResize 事件中。在 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。
    • 初始版本。需要您的改进建议。
© . All rights reserved.