zoukankan      html  css  js  c++  java
  • WinForm学习 简单的模拟时钟程序

    今天学习GDI+,试着想写一个模拟时钟的小程序,原以为很简单实现;但其实还有些复杂,特别是利用三角函数的那部分,让我四处找资料恶补了一下高中数学才算弄清楚,现在就回顾一下这个程序吧.

    程序的目的是要模拟出时钟的效果,那首先就是要画出这个时钟的样子。不考虑美观,一个时钟最简单的组成是一个圆形的表盘,三根直线代表的时针、分针和秒针。

    看起来很简单吧,但要怎么样画呢?让我们一步一步来吧:

    1.画表盘

    Graphics g = this.CreateGraphics(); //创建一个Graphics对象
    
    Pen myPen = new Pen(Color.Blue,1); //创建一个自定义画笔对象
    
    //创建一个正方形rect
    int myRect_X = this.ClientRectangle.Right/4;
    int myRect_Y = this.ClientRectangle.Bottom/4;
    int myRect_Width;
    int myRect_Height;
    if(this.ClientRectangle.Height< this.ClientRectangle.Width)
    {
        
        myRect_Width = this.ClientRectangle.Height/2;
        myRect_Height = this.ClientRectangle.Height/2;
    }
    else
    {
        myRect_Width = this.ClientRectangle.Width/2;
        myRect_Height = this.ClientRectangle.Width/2;
    }
    
    Rectangle rect = new Rectangle(myRect_X,myRect_Y,myRect_Width,myRect_Height);
    
    g.DrawEllipse(myPen,rect); //画出一个内切于矩形rect的圆

    Graphics.DrawEllipse方法有几种重载方式,其中较常用的就是Graphics.DrawEllipse(Pen,Rectangle),这个重载方法需要提供一个Rectangle参数,然后根据这个矩形的形状内切出椭圆,若这个矩形是正方形,则切出来的是一个正圆。

    这段程序最关键的地方就在于得到这个矩形了,因为它间接决定了表盘的位置与大小,Rectangle的构造函数需要四个参数,矩形左上角的X、Y坐标,宽度与高度。

    这里的ClientRectangle所代表的是控件的工作区,它也是一个Rectangle类型的对象,是指控件的边界减去非工作区元素(如滚动条、边框、标题栏和菜单)。相应的Right,Bottom,Width,Height分别代表了右边缘的X坐标,下边缘的Y坐标,宽度与高度。

    为了使这个表盘适应窗口的变化,这里将这些值全部设置为相对与ClientRectangle相关属性的值,如下图:

    这样当窗口大小变化时,这个圆的外切矩形也随之改变,假如这个窗口的宽度比高度大太多,或反之,则可能会出现表盘的部分超出工作区域无法显示,为了防止这种情况,加入了条件判断语句。如果宽度大于高度,则圆的直径取高度值;反之则取宽度值。当然高度等于宽度时,圆是在工作区域正中间。

    2.画表针

    将表盘画好后,就可以开始画表针了,这也是最复杂的一步

    //得到中心点坐标
    int centerPoint_X = rect.Right - rect.Width/2;
    int centerPoint_Y = rect.Bottom - rect.Height/2;
    Point centerPoint = new Point(centerPoint_X,centerPoint_Y);
    
    int s_Len = rect.Width*0.45; //秒针长度
    int m_Len = rect.Width*0.35//分针长度
    int l_Len = rect.Width*0.25 //时针长度
    
    //得到当前时间
    int h = DateTime.Now.Hour;
    int m = DateTime.Now.Minute;
    int s = DateTime.Now.Second;
    
    //得到秒针顶点坐标
    int sec_X = (int)(centerPoint.X + Math.Sin(6*Math.PI/180*s)*s_Len);
    int sec_Y = (int)(centerPoint.Y - Math.Cos(6*Math.PI/180*s)*s_Len);
    Point secPoint = new Point(sec_X,sec_Y);
    
    //得到分针顶点坐标
    int min_X = (int)(centerPoint.X + Math.Sin(6*Math.PI/180*m)*m_Len);
    int min_Y = (int)(centerPoint.Y - Math.Cos(6*Math.PI/180*m)*m_Len);
    Point minPoint = new Point(min_X,min_Y);
    
    //得到时针顶点坐标
    int hour_X = (int)(centerPoint.X + Math.Sin(h*30+m/2)*Math.PI/180*h_Len);
    int hour_Y = (int)(centerPoint.Y - Math.Cos(h*30+m/2)*Math.PI/180*h_Len);
    Point hourPoint = new Point(hour_X,hour_Y);
    
    //以不同的颜色连接
    myPen = new Pen(Color.Blue,1);
    g.DrawLine(myPen,centerPoint,secPoint); //连接原点和秒针顶点
    myPen = new Pen(Color.Green,2);
    g.DrawLine(myPen,centerPoint,minPoint); //连接原点和分针顶点
    myPen = new Pen(Color.Red,3);
    g.DrawLine(myPen,centerPoint,hourPoint); //连接原点和时针顶点
    g.Dispose(); //释放Graphics对象使用的所有资源

    要画出一条直线,必须要得到这个直线的两个顶点坐标,在这个程序里,三根表针有一个共同的原点(表盘中心点),然后如何得到另外一个点就是我们接下来要做的工作了。

    我们先来回顾一下三角函数的概念,sin值是 对边/斜边 得到,cos值是 邻边/斜边 得到,要求出这个点的坐标就是求出这个三角形的两条边,一条边的长度为X,另一条的长度为Y。现在我们已经知道的是斜边的长度,要求出这两条边,就要先知道弧度值。

    我们先来考虑最长的秒针,它每一秒都会移动一个角度,这个角度我们可以计算出来:

    360/60=6

    得到6度,这不难理解,因为整个圆是360度,一般的时钟在相邻小时之间又有5个刻度,所以一共是12*5=60个刻度,然后用360/60=6得到秒针一秒走6度,在程序里必须要将角度转化成弧度才能进行sin运算,所以还要乘以 PI/180(PI为圆周率)。

    有了这个弧度值,我们就可以得到每一秒秒针的顶点坐标了(以在第一区间考虑):

    sec_X = centerPoint.X + sin(6*PI/180)*当前秒钟值*s_Len
    sce_Y = centerPoint.Y - cos(6*PI/180)*当前秒钟值*s_Len

    接下来处理时针,因为我们这只是个很简单的模拟,所以就不需要对细节作过多的处理,就让它每过一分钟就跳到下一个刻度,这样求时针的顶点坐标和求秒针的就是一样的算法:

    min_X = centerPoint.X + sin(6*PI/180)*当前分钟值*m_Len
    min_Y = centerPoint.Y - cos(6*PI/180)*当前分钟值*m_Len

    时针的处理方式要不同,因为时针的取值只有12个数字,而分钟和秒针都有60个数字,如果以之前的方式处理时针,效果就是时针到点的那一瞬间它一下子转30度,当然不会有这样效果的时钟。那么我们该如何处理这个棘手的时针呢?

    假如现在是06:45:30,在整6点时它的度数是30*6,整7点是30*7,为了不至于将改变全在最后刻发生,我们加入时针值来参与计算,这样当前时间就是h*30+m/2,m为60时 m/2=30 ,也就是 m/2这个值在0--30之间,当角度变化为30度时,就指到了下一个整点。

    这样我们得到了时针的算法:

    hour_X = centerPoint.X + sin((h*30+m/2)*PI/180)*h_Len
    hour_Y = centerPoint.Y - cos((h*30+m/2)*PI/180)*h_Len

    最关键的代码处理了,剩下的工作就简单了,我们在程序里加入一个标签lblTime,来显示当前时间:

    string TimeInString = "";
    int hour = DateTime.Now.Hour;
    int min = DateTime.Now.Minute;
    int sec = DateTime.Now.Second;
    TimeInString = (hour<10)? "0"+hour.ToString():hour.ToString();
    TimeInString += (min<10)? "0"+min.ToString():min.ToString();
    TimeInString == (sec<10)? "0"+sec.ToString():sec.ToString();
    lblTime.Text = TimeInString; 

    程序最后运行效果:

  • 相关阅读:
    Java知识汇总第二天
    jvm学习笔记
    java知识汇总的第一天
    全链路压测流量模型
    FunTester测试框架Redis性能测试实践
    FunTester抄代码之路
    Jira API的踩坑记
    把工作讲给家人听
    颇具年代感的《JMeter中文操作手册》
    FunTester框架Redis压测预备
  • 原文地址:https://www.cnblogs.com/CoderWayne/p/4483994.html
Copyright © 2011-2022 走看看