一、Visual Studio创建一个WPF项目。
简单调整一下MainWindow.xaml文件。主要使用了两个Canvas控制,分别用于显示模拟和数字时钟,命名为AnalogCanvs、digitCanvas。代码如下:
<Window x:Class="MoonClock.MainWindow" ... Title="Moon Clock" Height="600" Width="1280" WindowStartupLocation="CenterScreen"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Canvas Grid.Column="0" Name="AnalogCanvs" Width="500" Height="500" /> <Canvas Grid.Column="1" Name="digitCanvas" Width="600" Height="300" /> </Grid> </Window>
二、模拟时钟先来。
(1)在MainWindow.xaml.cs先定义几个字段变量
// 共用字段 DispatcherTimer timer = new DispatcherTimer(); // 计时器 DateTime CurrTime = DateTime.Now; // 当前时间 // 模拟时钟字段定义 double radius = 250; // 圆半径 double angle = 360; // 角度 Point Opos = new Point(); // 原点位置 Line HourLine, MinuLine, SecdLine; // 时针、分针、秒针
这几个变量足够了。
(2)在构造函数初始化变量
public MainWindow() { InitializeComponent(); // 原点位置 Opos = new Point(250, 250); // 初始化计时器 timer.Interval = TimeSpan.FromMilliseconds(100); timer.Tick += Timer_Tick; // 初始化时钟针 HourLine = new Line(); MinuLine = new Line(); SecdLine = new Line(); }
(3)定义几个画表盘的方法
都是先定义图形,然后添加到AngleCanvas中显示出来
/// <summary> /// 画表盘外圆 /// </summary> private void DrawCircle() { Ellipse ellipse = new Ellipse(); ellipse.Stroke = Brushes.DarkGray; ellipse.StrokeThickness = 4; ellipse.Width = 500; ellipse.Height = 500; ellipse.Fill = Brushes.Gray; Canvas.SetLeft(ellipse, 0); Canvas.SetTop(ellipse, 0); AnalogCanvs.Children.Add(ellipse); Ellipse ellipse1 = new Ellipse(); ellipse1.Stroke = Brushes.Gray; ellipse1.StrokeThickness = 2; ellipse1.Width = 510; ellipse1.Height = 510; Canvas.SetLeft(ellipse1, -5); Canvas.SetTop(ellipse1, -5); AnalogCanvs.Children.Add(ellipse1); }
/// <summary> /// 圆形表心圆 /// </summary> private void DrawOCircle() { Ellipse ellipseO = new Ellipse(); ellipseO.Width = 30; ellipseO.Height = 30; ellipseO.Fill = Brushes.DarkGray; Canvas.SetLeft(ellipseO, Opos.X - 15); Canvas.SetTop(ellipseO, Opos.Y - 15); if (AnalogCanvs.Children.Contains(ellipseO)) AnalogCanvs.Children.Remove(ellipseO); AnalogCanvs.Children.Add(ellipseO); }
/// <summary> /// 画圆表盘数字 /// </summary> private void DrawDigit() { double x, y; for (int i=1; i<13; i++) { angle = WrapAngle(i * 360.0 / 12.0) - 90.0; angle = ConvertDegreesToRadians(angle); x = Opos.X + Math.Cos(angle) * (radius - 36) - 8; y = Opos.Y + Math.Sin(angle) * (radius - 36) - 15; TextBlock digit = new TextBlock(); digit.FontSize = 26; digit.Text = i.ToString(); // 数字12位置校正 if (i == 12) { Canvas.SetLeft(digit, x - 8); } else { Canvas.SetLeft(digit, x); } Canvas.SetTop(digit, y); AnalogCanvs.Children.Add(digit); } }
这里ConvertDegreesToRadians方法后面再定义,用于将角度值转换为弧度。
继续画:
/// <summary> /// 画圆表刻度 /// </summary> private void DrawGridLine() { double x1 = 0, y1 = 0; double x2 = 0, y2 = 0; for (int i = 0; i < 60; i++) { double angle1 = WrapAngle(i * 360.0 / 60.0) - 90; angle1 = ConvertDegreesToRadians(angle1); if (i % 5 == 0) { x1 = Math.Cos(angle1) * (radius - 20); y1 = Math.Sin(angle1) * (radius - 20); } else { x1 = Math.Cos(angle1) * (radius - 10); y1 = Math.Sin(angle1) * (radius - 10); } x2 = Math.Cos(angle1) * (radius - 5); y2 = Math.Sin(angle1) * (radius - 5); Line line = new Line(); line.X1 = x1; line.Y1 = y1; line.X2 = x2; line.Y2 = y2; line.Stroke = Brushes.Black; line.StrokeThickness = 3; Canvas.SetLeft(line, Opos.X); Canvas.SetTop(line, Opos.Y); AnalogCanvs.Children.Add(line); } }
以上两个画刻度和画表盘数字方法原理是一样的,就是先计算角度,再与半径计算为位置,之后将刻度或数字图形画到AngleCanvas中。
(4)是时候画时针了。
先来短黑粗的小时针
/// <summary> /// 画时针 /// </summary> private void DrawHourLine() { int hour = CurrTime.Hour; int minu = CurrTime.Minute; double dminu = minu / 60.0; // 根据分钟数增加时针偏移 double dhour = hour + dminu; double hour_angle = WrapAngle(dhour * (360.0 / 12.0) - 90.0); hour_angle = ConvertDegreesToRadians(hour_angle); double x = Math.Cos(hour_angle) * (radius - 100); double y = Math.Sin(hour_angle) * (radius - 100); HourLine.X1 = 0; HourLine.Y1 = 0; HourLine.X2 = x; HourLine.Y2 = y; HourLine.Stroke = Brushes.Black; HourLine.StrokeThickness = 16; Canvas.SetLeft(HourLine, Opos.X); Canvas.SetTop(HourLine, Opos.Y); if(AnalogCanvs.Children.Contains(HourLine)) { AnalogCanvs.Children.Remove(HourLine); } AnalogCanvs.Children.Add(HourLine); }
其中注释句用于根据分钟数增加小时指针的偏移。如果没有增加这一偏移,会是每小时跳一次小时指针,现实中的模拟时钟是不存在这种情况的。
再来秒钟指针:
/// <summary> /// 画秒针 /// </summary> private void DrawSecondLine() { int second = CurrTime.Second; // 秒针正方向点 double se_angle = WrapAngle(second * (360.0 / 60.0) - 90); se_angle = ConvertDegreesToRadians(se_angle); double sec_x = Math.Cos(se_angle) * (radius - 40); double sec_y = Math.Sin(se_angle) * (radius - 40); // 秒针反方向点 se_angle = WrapAngle(second * (360.0 / 60.0) + 90); se_angle = ConvertDegreesToRadians(se_angle); double sec_x_ = Math.Cos(se_angle) * (radius - 180); double sec_y_ = Math.Sin(se_angle) * (radius - 180); SecdLine.X1 = sec_x_; SecdLine.Y1 = sec_y_; SecdLine.X2 = sec_x; SecdLine.Y2 = sec_y; SecdLine.Stroke = Brushes.Red; SecdLine.StrokeThickness = 4; Canvas.SetLeft(SecdLine, Opos.X); Canvas.SetTop(SecdLine, Opos.Y); if (AnalogCanvs.Children.Contains(SecdLine)) { AnalogCanvs.Children.Remove(SecdLine); } AnalogCanvs.Children.Add(SecdLine); }
反方向点用于确定秒钟指针经过原点另一端的位置。一般秒钟在圆点两端都会伸出,只是两端长短不同而已。
分钟就留给有心人练手吧,这里就不贴了。
(5)接近最后——两个辅助方法
/// <summary> /// 角度360度进制 /// </summary> /// <param name="angle"></param> /// <returns></returns> private double WrapAngle(double angle) { return angle % 360; } /// <summary> /// 将角度转为弧度 /// </summary> /// <param name="degrees"></param> /// <returns></returns> private double ConvertDegreesToRadians(double degrees) { double radians = (Math.PI / 180) * degrees; return radians; }
(6)更新方法:方法中的几项内容是需要根据时间更新的。
/// <summary> /// 更新小时针、分针、秒针 /// </summary> private void Update() { DrawHourLine(); DrawMinuteLine(); DrawSecondLine(); DrawOCircle(); }
(7)定义计时器事件
/// <summary> /// 计时器事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Timer_Tick(object sender, EventArgs e) { // 更新当前时间 CurrTime = DateTime.Now; // 更新圆盘时针 Update(); }
看最后效果