zoukankan      html  css  js  c++  java
  • GDI绘制时钟效果,与系统时间保持同步,基于Winform

          2018年工作之余,想起来捡起GDI方面的技术,特意在RichCodeBox项目中做了两个示例程序,其中一个就是时钟效果,纯C#开发。这个CSharpQuartz是今天上午抽出一些时间,编写的,算是偷得浮生半日休闲吧。先来看看效果图吧:

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

    这是把时钟封装后,实现的一种效果,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的对象,具体代码如下:

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    /*=================================================================================================
    *
    * Title:C#开发的简易时钟
    * Author:李朝强
    * Description:模块描述
    * CreatedBy:lichaoqiang.com
    * CreatedOn:
    * ModifyBy:暂无...
    * ModifyOn:
    * Company:河南天一文化传播股份有限公司
    * Blog:http://www.lichaoqiang.com
    * Mark:
    *
    *** ================================================================================================*/
    namespace WinformGDIEvent.Sample
    {
    
        /// <summary>
        /// <![CDATA[CSharpQuarz GDI时钟]]>
        /// </summary>
        public class CSharpQuartz : IDisposable
        {
    
            /// <summary>
            /// 定时器
            /// </summary>
            private Thread timer = null;
    
            /// <summary>
            /// X坐标
            /// </summary>
            public float X { get; private set; }
    
            /// <summary>
            /// Y坐标
            /// </summary>
            public float Y { get; private set; }
    
            /// <summary>
            /// 半径
            /// </summary>
            private float r;
    
            /// <summary>
            /// 画布
            /// </summary>
            private Graphics Graphics = null;
    
            /// <summary>
            /// 直径
            /// </summary>
            public float Diameter { get; private set; }
    
            /// <summary>
            /// 
            /// </summary>
            public Form CurrentWinform { get; private set; }
    
    
            /// <summary>
            /// 
            /// </summary>
            private EventHandler _OnChanged;
    
            /// <summary>
            /// 事件,时钟状态更新后,当前频次1秒钟
            /// </summary>
            public event EventHandler OnChanged
            {
                add
                {
                    this._OnChanged += value;
                }
                remove
                {
                    this._OnChanged -= value;
                }
            }
    
    
            /// <summary>
            /// 构造函数
            /// </summary>
            CSharpQuartz()
            {
                //
                timer = new Thread((() =>
                {
                    if (Graphics == null)
                    {
                        Graphics = CurrentWinform.CreateGraphics();//创建画布
                        Graphics.SmoothingMode = SmoothingMode.HighQuality; //高质量
                        Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移质量
                    }
                    while (true)
                    {
                        //清除画布颜色,以窗体底色填充
                        Graphics.Clear(CurrentWinform.BackColor);
                        DrawCaller();//绘制时钟
                        //事件
                        if (_OnChanged != null) _OnChanged(this, null);
                        System.Threading.Thread.Sleep(1000);
                    }
                }));
                timer.IsBackground = true;
            }
    
            /// <summary>
            /// <![CDATA[构造函数]]>
            /// </summary>
            /// <param name="x"><![CDATA[圆x坐标]]></param>
            /// <param name="y"><![CDATA[圆y坐标]]></param>
            /// <param name="d"><![CDATA[圆直径]]></param>
            public CSharpQuartz(Form form, float x, float y, float d)
                : this()
            {
                this.CurrentWinform = form;
                this.X = x;
                this.Y = y;
                this.Diameter = d;
                r = d / 2;
            }
    
    
            /// <summary>
            /// 
            /// </summary>
            public void Start()
            {
                if (timer.IsAlive == false) timer.Start();//启动工作线程
            }
    
            /// <summary>
            /// 终止
            /// </summary>
            public void Stop()
            {
                if (timer.IsAlive == true) timer.Abort();//终止工作线程
            }
    
            /// <summary>
            /// <![CDATA[调用绘图]]>
            /// </summary>
            private void DrawCaller()
            {
                Graphics.SmoothingMode = SmoothingMode.HighQuality; //高质量
                Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移质量
                using (Pen pen = new Pen(Color.Red, 2))
                {
                    if (this.CurrentWinform.IsHandleCreated)
                    {
                        this.CurrentWinform.Invoke(new Action(() =>
                        {
                            //绘制圆
                            Graphics.DrawEllipse(pen, new RectangleF(X, Y, Diameter, Diameter));
    
                            //绘制象限
                            DrawQuadrant();
    
                            //绘制时、分、秒等针
                            DrawQuartzShot();
    
                            //绘制时刻线
                            DrawQuartzLine();
    
                            //写入版本信息
                            WriteVersion();
                        }));
                    }
                }
            }
    
    
            /// <summary>
            /// <![CDATA[绘制象限]]>
            /// </summary>
            private void DrawQuadrant()
            {
                #region  绘制象限
                float w = Diameter;
                float extend = 100f;
                using (Pen pen = new Pen(Color.Black, 1))
                {
    
                    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>
            private void DrawQuartzShot()
            {
                //当前时间
                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 radian = 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 = radian * 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 = radian * 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度角
                    }
                }
            }
    
    
            /// <summary>
            /// <![CDATA[绘制时刻线]]>
            /// </summary>
            private void DrawQuartzLine()
            {
                //圆心
                PointF pointEclipse = new PointF(X + r, Y + r);
                float labelX, labelY;//文本坐标
                float angle = Convert.ToSingle(Math.PI / 6);//角度 30度
                using (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);
                            }
                        }
                    }
                }
            }
    
    
            /// <summary>
            /// <![CDATA[写入版本信息]]>
            /// </summary>
            private void WriteVersion()
            {
                PointF point = new PointF(X + r / 4, Y + r - 30);
                using (Font font = new Font(FontFamily.GenericSansSerif, 18))
                {
                    using (Brush brush = new SolidBrush(Color.Black))
                    {
                        this.Graphics.DrawString("Quartz", font, brush, point);
                    }
                }
            }
    
    
            /// <summary>
            /// <![CDATA[释放]]>
            /// </summary>
            /// <param name="isDispose"></param>
            private void Dispose(bool isDispose)
            {
                if (isDispose)
                {
                    timer.Abort();
                    this.Graphics.Dispose();
                }
            }
    
            /// <summary>
            /// 
            /// </summary>
            public void Dispose()
            {
                this.Dispose(true);
            }
        }
    }
    

      winfom调用示例

        /// <summary>
            /// 
            /// </summary>
            private CSharpQuartz sharpQuartz = null;
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void CSharpQuartzSample_Load(object sender, EventArgs e)
            {
                float w = 300f, h = 300f;
                float x = (this.Width - w) / 2;
                float y = (this.Height - h) / 2;
                sharpQuartz = new CSharpQuartz(this, x, y, w);
                sharpQuartz.OnChanged += SharpQuartz_OnChanged;
                sharpQuartz.Start();
            }
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void SharpQuartz_OnChanged(object sender, EventArgs e)
            {
                if (lblTime.IsHandleCreated)
                {
                    lblTime.Invoke(new Action(() =>
                    {
                        lblTime.Text = DateTime.Now.ToString("当前时间:HH:mm:ss");
                    }));
                }
            }
    

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

  • 相关阅读:
    移动端前端开发调试
    Safari 前端开发调试 iOS 完美解决方案
    IOS下移除按钮原生样式 -webkit-appearance
    修复iPhone的safari浏览器上submit按钮圆角bug
    解决 placeholder 垂直不居中,偏上的问题
    如何使用JavaScript和正则表达式进行数据验证
    关于VSS(Volume Shadow Copy Service)一
    centOS目录结构
    如何解决windows 80端口被占用的情况
    linux系统TCP协议之Send(转)
  • 原文地址:https://www.cnblogs.com/ibeisha/p/csharquartz.html
Copyright © 2011-2022 走看看