GDI绘制时钟效果,与系统时间保持同步,基于Winform

…衆ロ難τιáo~ 提交于 2020-01-07 07:38:02

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

这是直接在Winform的基础上进行绘制的。接下来,我对时钟进行了封装,封装成一个名为CSharpQuartz的类,效果如下:

C#

这是把时钟封装后,实现的一种效果,CSharpQuartz内部开辟了一个线程,与系统时间,保持同步,每秒刷新一次。所采用的技术也就是GDI和多线程及事件委托。把时钟封装成对象后,还为其添加了OnChanged事件,用于对象提供外部

处理之用。接下来就简单的说下,做次小程序的一些准备工作吧。

           这也是最近偶尔听到有朋友问怎样做时钟的事,想来,其实也简单的,只是需要一些耐心和细心,这里主要还利用一些三角函数进行计算。上图看似很简单,其实也有很多小细节需要注意。我就把大致绘制的过程简单说下:

          首先,我们需要定义一个圆,来作为时钟的轮廓,这里是通过设置时钟的直径及winform的宽高,来计算出时钟在窗体居中的位置。绘制圆的代码就更简单了

float w = 300f, h = 300f;
float x = (this.Width - w) / 2;
float y = (this.Height - h) / 2;
 
float d = w;//直径
float r = d / 2;//半径
 
graphics.DrawEllipse(pen, new RectangleF(x, y, w, h));//绘制圆

接下来,我们需要计算圆的一周遍布的12个时间点。然后把这些时间点和圆心连在一起,就形成了上图我们看到的不同时间点的线段。圆心的查找非常简单,圆心的坐标点,其实就是x轴+半径r,y轴+半径r:

	
PointF pointEclipse = new PointF(x + r, y + r);

开始分表绘制12个点与圆心的连线,我这里是以9点为起点,此时,脑海中呈现这样的画面

时针一圈12格,每格也就是 Math.PI/6

比如我们计算10点在圆上的坐标P10.

10点所在的点,与x轴的夹角呈30度。那么10点在x上的坐标应该是,x1=x+r-r*Cos(30),以此类推,不难求出12个点的位置,具体代码如下:

/// <summary>
/// <![CDATA[画时刻线 这里是以9点这个时间坐标为起点 进行360度]]>
/// </summary>
/// <param name="graphics"><![CDATA[画布]]></param>
/// <param name="x"><![CDATA[圆x坐标]]></param>
/// <param name="y"><![CDATA[圆y坐标]]></param>
/// <param name="r"><![CDATA[圆x坐标]]></param>
private void DrawQuartzLine(Graphics graphics, float x, float y, float r)
{
    //圆心
    PointF pointEclipse = new PointF(x + r, y + r);
    float labelX, labelY;//文本坐标
    float angle = Convert.ToSingle(Math.PI / 6);//角度 30度
    Font font = new Font(FontFamily.GenericSerif, 12);
    float _x, _y;//圆上的坐标点
    using (Brush brush = new System.Drawing.SolidBrush(Color.Red))
    {
        using (Pen pen = new Pen(Color.Black, 0.6f))
        {
            //一天12H,将圆分为12份 
            for (int i = 0; i <= 11; i++)
            {
                PointF p10;//圆周上的点
                float pAngle = angle * i;
                float x1, y1;
 
                //三、四象限
                if (pAngle > Math.PI)
                {
                    if ((pAngle - Math.PI) > Math.PI / 2)//钝角大于90度 
                    {
                        //第三象限
                        x1 = Convert.ToSingle(r * Math.Cos(Math.PI * 2 - pAngle));
                        y1 = Convert.ToSingle(r * Math.Sin(Math.PI * 2 - pAngle));
                        _x = x + r - x1;
                        _y = y + r + y1;
                        labelX = _x - 8;
                        labelY = _y;
                    }
                    else
                    {
                        //第四象限
                        x1 = Convert.ToSingle(r * Math.Cos(pAngle - Math.PI));
                        y1 = Convert.ToSingle(r * Math.Sin(pAngle - Math.PI));
                        _x = x + r + x1;
                        _y = y + r + y1;
                        labelX = _x;
                        labelY = _y;
                    }
                }
                //一、二象限
                else if (pAngle > Math.PI / 2)//钝角大于90度
                {
                    //第一象限
                    x1 = Convert.ToSingle(r * Math.Cos(Math.PI - pAngle));
                    y1 = Convert.ToSingle(r * Math.Sin(Math.PI - pAngle));
                    _x = x + r + x1;
                    _y = y + r - y1;
                    labelX = _x;
                    labelY = _y - 20;
                }
                else
                {
                    //第二象限
                    x1 = Convert.ToSingle(r * Math.Cos(pAngle));
                    y1 = Convert.ToSingle(r * Math.Sin(pAngle));
                    _x = x + r - x1;
                    _y = y + r - y1;
                    labelX = _x - 15;
                    labelY = _y - 20;
                }
                //上半圆 分成12份,每份 30度
                if (i + 9 > 12)
                {
                    graphics.DrawString((i + 9 - 12).ToString(), font, brush, labelX, labelY);
                }
                else
                {
                    if (i + 9 == 9)
                    {
                        labelX = x - 13;
                        labelY = y + r - 6;
                    }
                    graphics.DrawString((i + 9).ToString(), font, brush, labelX, labelY);
                }
                p10 = new PointF(_x, _y);
                graphics.DrawLine(pen, pointEclipse, p10);
            }
        }
    }
}

为了辅助计算,我又添加了x轴与y轴的线,就是我们在效果图中看到的垂直于水平两条线段。

/// <summary>
/// <![CDATA[绘制象限]]>
/// </summary>
/// <param name="graphics"><![CDATA[画布]]></param>
/// <param name="x"><![CDATA[圆x坐标]]></param>
/// <param name="y"><![CDATA[圆y坐标]]></param>
/// <param name="r"><![CDATA[圆半径]]></param>
private void DrawQuadrant(Graphics graphics, float x, float y, float r)
{
    float w = r * 2;
    float extend = 100f;
    using (Pen pen = new Pen(Color.Black, 1))
    {
        #region  绘制象限
        PointF point1 = new PointF(x - extend, y + r);//
        PointF point2 = new PointF(x + w + extend, y + r);
 
        PointF point3 = new PointF(x + r, y - extend);//
        PointF point4 = new PointF(x + r, y + w + extend);
 
        graphics.DrawLine(pen, point1, point2);
 
        graphics.DrawLine(pen, point3, point4);
        #endregion 绘制象限
    }
 
}

接下来,该绘制指针(时、分、秒),就是我们效果图中看到的,红绿蓝,三条长短不一的线段,秒针最长,这是和本地系统时间同步,所以要根据当前时间,计算出指针所在的位置。

/// <summary>
/// <![CDATA[绘制时、分、秒针]]>
/// </summary>
/// <param name="graphics"><![CDATA[画布]]></param>
/// <param name="x"><![CDATA[圆x坐标]]></param>
/// <param name="y"><![CDATA[圆y坐标]]></param>
/// <param name="r"><![CDATA[圆半径]]></param>
private void DrawQuartzShot(Graphics graphics, float x, float y, float r)
{
    if (this.IsHandleCreated)
    {
        this.Invoke(new Action(() =>
        {
            //当前时间
            DateTime dtNow = DateTime.Now;
            int h = dtNow.Hour;
            int m = dtNow.Minute;
            int s = dtNow.Second;
            float ha = Convert.ToSingle(Math.PI * 2 / 12);//每小时所弧度 360/12格=30
            float hm = Convert.ToSingle(Math.PI * 2 / 60);
            float hs = Convert.ToSingle(Math.PI * 2 / 60);
            float x1, y1, offset = 60f;
            using (Pen pen = new Pen(Color.Green, 4))
            {
                //时针
                h = h >= 12 ? h - 12 : h;
                double angle = h * ha;//当前时针所占弧度
                x1 = x + r + Convert.ToSingle(Math.Sin(angle) * (r - offset));//通过调整offset的大小,可以控制时针的长短
                y1 = y + r - Convert.ToSingle(Math.Cos(angle) * (r - offset));
                //圆心
                PointF pointEclipse = new PointF(x + r, y + r);
                PointF pointEnd = new PointF(x1, y1);
 
                graphics.DrawLine(pen, pointEclipse, pointEnd);//画45度角
 
                //分针
                using (Pen penYellow = new Pen(Color.Red, 2))
                {
                    offset = 30;
                    //分
                    double angelMinutes = hm * m;//每分钟弧度
                    x1 = x + r + Convert.ToSingle(Math.Sin(angelMinutes) * (r - offset));//通过调整offset的大小,可以控制时针的长短
                    y1 = y + r - Convert.ToSingle(Math.Cos(angelMinutes) * (r - offset));
                    graphics.DrawLine(penYellow, pointEclipse, new PointF(x1, y1));//画45度角
                }
 
                //秒针
                using (Pen penYellow = new Pen(Color.Blue, 2))
                {
                    offset = 20;
                    //分
                    double angelSeconds = hs * s;//每秒钟弧度
                    x1 = x + r + Convert.ToSingle(Math.Sin(angelSeconds) * (r - offset));//通过调整offset的大小,可以控制时针的长短
                    y1 = y + r - Convert.ToSingle(Math.Cos(angelSeconds) * (r - offset));
                    graphics.DrawLine(penYellow, pointEclipse, new PointF(x1, y1));//画45度角
                }
 
            }
 
            this.lblTime.Text = string.Format("当前时间:{0}:{1}:{2}", h, m, s);
        }));
 
    }
}

最后,开辟一个线程,来同步更新时针的状态。

/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Quartz_Load(object sender, EventArgs e)
{
    timer = new Thread(() =>
    {
        if (_graphics == null)
        {
            _graphics = this.CreateGraphics();
            _graphics.SmoothingMode = SmoothingMode.HighQuality; //高质量
            _graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移质量
        }
        while (true)
        {
            _graphics.Clear(this.BackColor);
            DrawCaller(_graphics);
            System.Threading.Thread.Sleep(1000);
        }
    });
    timer.IsBackground = true;
}

每秒钟,更新一次,其实就是重绘。

      完成了以上几个步骤,我们就完成GDI绘制时钟的工作,后来,把它封装成一个名为CSharpQuartz的对象,具体代码已经托管到码云上,点击此处

     这就是我们开篇第一张效果图,带有Quartz字样的,至此,关于GDI绘制时钟与系统时间同步的小程序就这样完成。时间仓促,某些计算方法买来得及仔细推敲,不足之处,大家多提意见。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!