C#绘制时钟
猫猫的小茶馆 人气:0时钟是这样的
一共使用四个控件即可:
WinFrom窗体应用程序代码:
using SpeechLib; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace MyClockTest { public partial class Form1 : Form { //创建对象myclock MyClock myclock = new MyClock(); //定义date1,共电子表使用 -- 获取到当下时间点 DateTime date1 = DateTime.Now; //Now 为静态只读属性(属性可计算) //定义中心点center和半径radius -- 时钟的圆心和半径大小 Point center = new Point(160, 200); // 圆心点的坐标 int radius = 150; // 半径 150 //定义判定是否击中表盘的bool变量 -- 鼠标点击拖拽功能 bool down = false; //记录鼠标点击的点 Point pre; //程序入口 public Form1() { InitializeComponent(); } //开始画时钟 private void OnPaint(object sender, PaintEventArgs e) { //这里不使用Graphics,而选择使用BufferedGraphics: //Graphics和BufferedGraphics对比: //5000次下时Graphics效率高一些;5000次以后BufferedGraphics效率高一些 //Bitmap bmp = new Bitmap(width ,height ); //Graphics g2 = Graphics.FromImage(bmp ); //myclock.Draw(g2); //g2.CreateGraphics(); //g2.DrawImage(bmp,0,0); //bmt.dispose(); //Image iamge = new Bitmap(this.Width ,this .Height ); //Graphics g1 = Graphics.FromImage(iamge); //g1.Clear(Form .DefaultBackColor ); //C#双缓冲解释: //简单说就是当我们在进行画图操作时,系统并不是直接把内容呈现到屏幕上,而是先在内存中保存,然后一次性把结果输出来。 //如果没用双缓冲的话,你会发现在画图过程中屏幕会闪的很厉害,因为后台一直在刷新, //而如果等用户画完之后再输出就不会出现这种情况,具体的做法,其实也就是先创建一个位图对象,然后把内容保存在里面,最后把图呈现出来。 //解决空间重绘时闪烁的问题,并在改变大小时重绘图形 this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint, true); //矩形绘图区域 Rectangle rect = e.ClipRectangle; //关于BufferedGraphicsContext类: // 手工设置双缓冲.netframework提供了一个类BufferedGraphicsContext负责单独分配和管理图形缓冲区。 // 每个应用程序域都有自己的默认 BufferedGraphicsContext 实例来管理此应用程序的所有默认双缓冲。 // 大多数情况下,每个应用程序只有一个应用程序域,所以每个应用程序通常只有一个默认 BufferedGraphicsContext。 //关于此处的绘图: //通过管理BufferedGraphicsContext实现双缓冲的步骤如下: //(1)获得对 BufferedGraphicsContext 类的实例的引用。 //(2)通过调用 BufferedGraphicsContext.Allocate 方法创建 BufferedGraphics 类的实例。 //(3)通过设置 BufferedGraphics.Graphics 属性将图形绘制到图形缓冲区。 //(4)当完成所有图形缓冲区中的绘制操作时,可调用 BufferedGraphics.Render 方法将缓冲区的内容呈现到与该缓冲区关联的绘图图面或者指定的绘图图面。 //(5)完成呈现图形之后,对 BufferedGraphics 实例调用释放系统资源的 Dispose 方法。 BufferedGraphicsContext current = BufferedGraphicsManager.Current; //(1) BufferedGraphics bg; bg = current.Allocate(e.Graphics, e.ClipRectangle); //(2) Graphics g = bg.Graphics; //(3) //定义画布 //Graphics g = CreateGraphics(); //消除锯齿 //g.SmoothingMode = SmoothingMode.AntiAlias; g.SmoothingMode = SmoothingMode.HighQuality; //高质量 g.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移质量 //每次绘画后的背景色填充 && 定义画电子钟的字体 g.Clear(this.BackColor); Font font = new Font("Times New Roman", 20); //在每次绘画时,更新时间,半径及中心点 myclock.Date = DateTime.Now; myclock.Week = DateTime.Now; myclock.P = center; myclock.R = radius; //调用myclock中的Draw函数画出钟 myclock.Draw(g); //直接调用DrawString函数,显示电子表,并且设置显示格式 g.DrawString(date1.ToString(), font, Brushes.Black, 10, 10);//10,10表示字的左上角在哪 bg.Render(e.Graphics); //(4)---> 开启绘制 bg.Dispose(); //(5)---> 绘制完成,释放资源 //如果为整点,则报时 if (Convert.ToString(DateTime.Now.Minute) == "0" && Convert.ToString(DateTime.Now.Second ) == "0") { SpVoice voice = new SpVoice(); voice.Speak("现在时间为" + Convert.ToString(DateTime.Now.Month) + "月" + Convert.ToString(DateTime.Now.Day) + "日" + Convert.ToString(DateTime.Now.Hour) + "点" + Convert.ToString(DateTime.Now.Minute) + "分", SpeechVoiceSpeakFlags.SVSFDefault); } } //更新时钟的时间 private void OnTime(object sender, EventArgs e) { //使用timer控件来更新时间 ---> 1s/次 //更新钟的时间 myclock.Date = DateTime.Now; myclock.Week = DateTime.Now; //更新电子表的时间 date1 = DateTime.Now; //刷新 --- 每走动一秒都更新画布(重绘) Invalidate(); //Refresh(); } //鼠标单击到时钟上的坐标 private void Form1_MouseDown(object sender, MouseEventArgs e) { //记录点击的点,并且计算是否击中时钟 -- 记录点击时的坐标 pre = new Point(e.X, e.Y); down = myclock.HitTest(pre, center, radius); } //在点击后,鼠标拖动时 private void Form1_MouseMove(object sender, MouseEventArgs e) { //使用if来判断是否击中,若击中,更新中心点 Point p = new Point(e.X, e.Y); //如果点击持续 if (down) { //更新圆心坐标点 center = new Point(center.X + p.X - pre.X, center.Y + p.Y - pre.Y); //pre = 当前移动的点的坐标 pre = p; //重绘 Invalidate(); //Refresh(); } } private void Form1_MouseUp(object sender, MouseEventArgs e) { //取消击中 -- 拖拽结束 down = false; } private void trackBar1_Scroll(object sender, EventArgs e) { //使用 trackBar1来控制半径大小,并且更新 ---> trackBar控件改变时钟的大小 radius = trackBar1.Value; Invalidate(); //refresh } //单击:报时功能 private void button1_Click(object sender, EventArgs e) { DateTime da = new DateTime(); da = DateTime.Now; //SpVoice报时 SpVoice voice = new SpVoice(); voice.Speak("现在时间为" + Convert.ToString(da.Month) + "月" + Convert.ToString(da.Day) + "日" + Convert.ToString(da.Hour) + "点" + Convert.ToString(da.Minute) + "分", SpeechVoiceSpeakFlags.SVSFDefault); } } //初始化时钟:类应该写在外面(此处不规范) public class MyClock { enum time { XII, III, VI, IX } //定义结构体 -- 时钟的4顶点 public DateTime date; //Now 为静态只读{get}属性(属性可计算),智能作为右值 -- 电子表时间 public DateTime week; // 周 public Point p; // 圆心点的坐标 public int r; // 半径 public MyClock() { //定义一个构造函数,赋予date当前时间 date = DateTime.Now; week = DateTime.Now; p = new Point(160, 200); // 圆心点的坐标 r = 150; // 半径 } //画表盘 public void DrawDial(Graphics g) { //定义两个pen,p1用来画小刻度及表盘,p2用来画大刻度 Pen p1 = new Pen(Color.Black, 2); Pen p2 = new Pen(Color.Green, 2); //时钟顶点时间的字体大小 float f = r / 10; //字体样式 && 字体大小 Font font = new Font("Times New Roman", f); //定义两个点数组来存储刻度线的两点 --- [60] 数组中共存放了60个点的坐标 PointF[] pointf1 = new PointF[60]; PointF[] pointf2 = new PointF[60]; // for(int i = 0; i < 59; i++) { //通过for循环来给pointf1赋值 ---> f1点就在圆形的边上,r float x1 = (float)(p.X + r * Math.Sin((i + 1) * Math.PI * 2 / 60)); float y1 = (float)(p.Y + r * Math.Cos((i + 1) * Math.PI * 2 / 60)); pointf1[i] = new PointF(x1, y1); //使用if来判断,能整除5就画大刻度,不能就画小刻度 if ((i + 1) % 5 == 0) { float x2 = (float)(p.X + (r - r / 20) * Math.Sin((i + 1) * 2 * Math.PI / 60)); float y2 = (float)(p.Y + (r - r / 20) * Math.Cos((i + 1) * 2 * Math.PI / 60)); pointf2[i] = new PointF(x2, y2); //画大刻度 g.DrawLine(p2, pointf1[i], pointf2[i]); } else { float x2 = (float)(p.X + (r - r / 40) * Math.Sin((i + 1) * 2 * Math.PI / 60)); float y2 = (float)(p.Y + (r - r / 40) * Math.Cos((i + 1) * 2 * Math.PI / 60)); pointf2[i] = new PointF(x2, y2); //画小刻度 g.DrawLine(p2, pointf1[i], pointf2[i]); } } //用pointf3来储存四个点的位置 表盘的4个顶点位置 ---> 横坐标 && 纵坐标调整距离到合适的位置上 PointF[] pointf3 = { new PointF (pointf1 [30].X-f/4,pointf1 [30].Y +r/20), new PointF (pointf1 [15].X-r/20-2*f ,pointf1 [15].Y+f/3 ), new PointF (pointf1 [0].X-2*f ,pointf1 [0].Y -r/20-4*f/3), new PointF (pointf1 [45].X+r/20 ,pointf1 [45].Y -r/20-4*f/3), }; //画出四个点相应的时间值 for (int m = 0; m < 4; m++) { g.DrawString(((time)m).ToString(), font, Brushes.Black, pointf3[m]); } //代码重复 ---> 代码保持高内聚低耦合 ---> (time)将阿拉伯数字改成时间类型 //g.DrawString(((time)0).ToString(), font, Brushes.Black, pointf3[0]); //g.DrawString(time.III.ToString(), font, Brushes.Black, pointf3[1]); //g.DrawString(time.VI.ToString(), font, Brushes.Black, pointf3[2]); //g.DrawString(time.IX.ToString(), font, Brushes.Black, pointf3[3]); //使用DrawEllipse画表盘 --- pen 左上角的x坐标 左上角的y坐标 宽度 高度 ---> 画圆 g.DrawEllipse(p1, p.X - r, p.Y - r, 2 * r, 2 * r); } //画时针 public void DrawHourPointer(Graphics g) { //定义画笔p1,设置其属性,使其带有箭头,圆点尾部 Pen p1 = new Pen(Color.Green, 4); p1.EndCap = LineCap.ArrowAnchor; // 箭头 p1.StartCap = LineCap.Round; // 尾部 //获取分钟,转化为int型 int i = Convert.ToInt32(date.Minute); //获取小时,并转换为int型,同时对12取余,获得十二小时制的小时数 int j = (Convert.ToInt32(date.Hour)) % 12; //计算点值 --- (2 * Math.PI) * j / 720) 根据分钟表示时针的位置 ---> 1分钟再小时里占比1/720 ---> 60分钟*12个小时数 = 720 //点1的坐标 float x1 = (float)(p.X + (r - r / 3) * Math.Sin((j * 2 * Math.PI / 12) + (2 * Math.PI) * i / 720)); float y1 = (float)(p.Y - (r - r / 3) * Math.Cos((j * 2 * Math.PI / 12) + (2 * Math.PI) * i / 720)); //点2的坐标 float x2 = (float)(p.X - r / 30 * Math.Sin((j * 2 * Math.PI / 12) + (2 * Math.PI) * i / 720)); float y2 = (float)(p.Y + r / 30 * Math.Cos((j * 2 * Math.PI / 12) + (2 * Math.PI) * i / 720)); PointF pointf1 = new PointF(x1, y1); PointF pointf2 = new PointF(x2, y2); //用DrawLine画时针 g.DrawLine(p1, pointf2, pointf1); } //画分针 public void DrawMinutePointer(Graphics g) { //定义画笔p1,设置其属性,使其带有箭头,圆点尾部 //画笔宽度为3 ---> 分针比时针细一点 Pen p1 = new Pen(Color.Blue, 3); p1.EndCap = LineCap.ArrowAnchor; p1.StartCap = LineCap.Round; //获取当前分钟,并转换为int型 int i = Convert.ToInt32(date.Minute); //计算点值 //点1 float x1 = (float)(p.X + (r - r / 5) * Math.Sin(i * 2 * Math.PI / 60)); float y1 = (float)(p.Y - (r - r / 5) * Math.Cos(i * 2 * Math.PI / 60)); //点2 float x2 = (float)(p.X - r / 20 * Math.Sin(i * 2 * Math.PI / 60)); float y2 = (float)(p.Y + r / 20 * Math.Cos(i * 2 * Math.PI / 60)); PointF pointf1 = new PointF(x1, y1); PointF pointf2 = new PointF(x2, y2); //用DrawLine画分针 g.DrawLine(p1, pointf2, pointf1); } //画秒针 public void DrawSecondPointer(Graphics g) { Pen p1 = new Pen(Color.Red, 2); p1.EndCap = LineCap.ArrowAnchor; p1.StartCap = LineCap.Round; //获取当前秒,并转换为int型 int i = Convert.ToInt32(date.Second); //计算点值 float x1 = (float)(p.X + (r - r / 20) * Math.Sin(i * 2 * Math.PI / 60)); float y1 = (float)(p.Y - (r - r / 20) * Math.Cos(i * 2 * Math.PI / 60)); float x2 = (float)(p.X - r / 15 * Math.Sin(i * 2 * Math.PI / 60)); float y2 = (float)(p.Y + r / 15 * Math.Cos(i * 2 * Math.PI / 60)); PointF pointf1 = new PointF(x1, y1); PointF pointf2 = new PointF(x2, y2); //用DrawLine画秒针 ---> 根据两点之间画线 g.DrawLine(p1, pointf2, pointf1); } //周和日期显示 ---> 表盘上 public void DrawWeeks(Graphics g) { //字体大小 float f = r / 15; Font font = new Font("Times New Roman", f); //放置位置 float f1 = (float)(p.X + r * Math.Sin(Math.PI) / 2); float f2 = (float)(p.Y - r * Math.Cos(Math.PI) / 2); //DrawString ---> 画字符串 g.DrawString(week.DayOfWeek.ToString(), font, Brushes.Black, f1, f2); g.DrawString(week.Day.ToString(), font, Brushes.Black, f1 + 80, f2); } public void Draw(Graphics g) { //定义函数Draw,画出时钟 //画表盘 DrawDial(g); //画时针 DrawHourPointer(g); //画分针 DrawMinutePointer(g); //画秒针 DrawSecondPointer(g); //画周和日期 DrawWeeks(g); } public bool HitTest(Point p, Point center, int r) { //HitTest函数表示鼠标是否点在时钟里面 //通过计算鼠标点击的点到中心点的距离和半径的比较来判定是否击中时钟 //圆心到半径的距离画圆,便是圆的全部面积 double f1 = (double)(p.X - center.X); double f2 = (double)(p.Y - center.Y); //test即为鼠标点击的点到中心点的距离 //根据勾股定理,求距离,直角三角形 a² + b² = c² double test = Math.Sqrt(f1 * f1 + f2 * f2); //斜边长于圆的半径,则在圆的范围之外;斜边小于圆的半径,则在圆的范围之内 if (test <= r) { return true; } else { return false; } } #region 属性 //此处设置成了只读 ---> 不可写 ---> 导致时钟的DateTime时间无法写入进来 //public DateTime Date { get; internal set; } //public DateTime Week { get; internal set; } //public Point P { get; internal set; } //public int R { get; internal set; } public DateTime Date { set { date = value; } } public Point P { set { p = value; } } public int R { set { r = value; } } public DateTime Week { set { week = value; } } #endregion } }
加载全部内容