制作简单的WPF时钟_wpf中时钟控件,可以绑定system.datetime.now吗-程序员宅基地

技术标签: WPF研究  timer  GDI+图形类  binding  wpf  class  object  gdi+  

在很早之前,我曾经写过一个GDI+的时钟,见“C#时钟控件 (C# Clock Control)” http://blog.csdn.net/johnsuna/archive/2006/02/13/597746.aspx及“使用C#编写LED样式时钟控件”(http://blog.csdn.net/johnsuna/archive/2006/02/14/598867.aspx),进入WPF时代了,如何用WPF绘制一个时钟呢?

先看效果:

WPF时钟

上面显示的是时间值,下面是图形版的时钟。

制作要点:
1. 首先在Expression Blend中画出时钟的样式;
2. 建立时钟的程序辅助类;
3. 将此时钟样式需要动态换掉的部分改成相应的绑定值。

由于具体步骤很多,这里只说说技术难点和要点,着重说明上述第2点部分。
// 时钟控件类:Clock.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Timers;
using System.Windows.Threading;

namespace BrawDraw.Com.WPF.Clock.ControlLibrary
{
    /// <summary>Clock Control
 /// </summary>

 public class Clock : Control
 {
  private DispatcherTimer timer;

  static Clock()
  {
   DefaultStyleKeyProperty.OverrideMetadata(typeof(Clock), new FrameworkPropertyMetadata(typeof(Clock)));
  }

  protected override void OnInitialized(EventArgs e)
  {
   base.OnInitialized(e);
   timer = new DispatcherTimer();
   timer.Tick += new EventHandler(Timer_Tick);
   timer.Start();
  }

  private void Timer_Tick(object sender, EventArgs e)
  {
   UpdateDateTime();
   timer.Interval = TimeSpan.FromMilliseconds(1000 - DateTime.Now.Millisecond);
  }

  private void UpdateDateTime()
  {
   this.DateTime = System.DateTime.Now;
  }

  #region DateTime property
  public DateTime DateTime
  {
   get
   {
    return (DateTime)GetValue(DateTimeProperty);
   }
   private set
   {
    SetValue(DateTimeProperty, value);
   }
  }

  public static DependencyProperty DateTimeProperty = DependencyProperty.Register(
    "DateTime",
    typeof(DateTime),
    typeof(Clock),
    new PropertyMetadata(DateTime.Now, new PropertyChangedCallback(OnDateTimeInvalidated)));

  public static readonly RoutedEvent DateTimeChangedEvent =
   EventManager.RegisterRoutedEvent("DateTimeChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<DateTime>), typeof(Clock));

  protected virtual void OnDateTimeChanged(DateTime oldValue, DateTime newValue)
  {
   RoutedPropertyChangedEventArgs<DateTime> args = new RoutedPropertyChangedEventArgs<DateTime>(oldValue, newValue);
   args.RoutedEvent = Clock.DateTimeChangedEvent;
   RaiseEvent(args);
  }

  private static void OnDateTimeInvalidated(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
   Clock clock = (Clock)d;

   DateTime oldValue = (DateTime)e.OldValue;
   DateTime newValue = (DateTime)e.NewValue;

   clock.OnDateTimeChanged(oldValue, newValue);
  }
  #endregion
 }
}

// 时钟内部时针、分针、秒针角度计算及星期显示的类: ClockConverters.cs
// 由于WPF中旋转角度是以度单位,计算为绕一个圆周时,为360度。所以,计算时以360度来计算。
using System;
using System.Globalization;
using System.Windows.Data;

namespace BrawDraw.Com.WPF.Clock
{
    // 一分钟60秒,一周为360度,所以,一秒钟就占6度,所以是:秒数×6。
    [ValueConversion(typeof(DateTime), typeof(int))]
    public class SecondsConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime)value;
            return date.Second * 6;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }

// 一小时是60分钟,一周为360度,所以,一分钟就占6度,所以是:分钟数×6。
    [ValueConversion(typeof(DateTime), typeof(int))]
    public class MinutesConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime)value;
            return date.Minute * 6;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }

// 时钟上显示12小时,一周为360度,这样,每小时占30度,考虑分钟所占角度是分钟数/2,所以结果是:小时数×30 + 分钟数/2。
    [ValueConversion(typeof(DateTime), typeof(int))]
    public class HoursConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime)value;
            return (date.Hour * 30) + (date.Minute / 2);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }

    [ValueConversion(typeof(DateTime), typeof(string))]
    public class WeekdayConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime)value;
            return date.DayOfWeek.ToString().Substring(0,3);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }
}

剩下的就十分简单,程序的显示部分(XAML代码)
// MainWindow.xaml
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:customControl="clr-namespace:BrawDraw.Com.WPF.Clock.ControlLibrary;assembly=CustomControlLibrary"
 xmlns:local="clr-namespace:BrawDraw.Com.WPF.Clock"
    x:Class="BrawDraw.Com.WPF.Clock.Window1"
    Title="WPF Clock" Height="394" Width="332"
    >
 <StackPanel HorizontalAlignment="Center">
  <StackPanel.Resources>
   <local:SecondsConverter x:Key="secondsConverter"/>
   <local:MinutesConverter x:Key="minutesConverter"/>
   <local:HoursConverter x:Key="hoursConverter"/>
   <local:WeekdayConverter x:Key="weekdayConverter"/>
   <Style x:Key="AnalogClock" TargetType="{x:Type customControl:Clock}">

    <Setter Property="Template">
     <Setter.Value>
      <ControlTemplate TargetType="{x:Type customControl:Clock}">
       <Grid>
        <Grid.ColumnDefinitions>
         <ColumnDefinition Width="108" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
         <RowDefinition Height="108" />
        </Grid.RowDefinitions>
        <Ellipse Width="108" Height="108" StrokeThickness="3" Grid.Row="0" Grid.Column="0">
         <Ellipse.Stroke>
          <LinearGradientBrush>
           <LinearGradientBrush.GradientStops>
            <GradientStop Color="LightBlue" Offset="0" />
            <GradientStop Color="DarkBlue" Offset="1" />
           </LinearGradientBrush.GradientStops>
          </LinearGradientBrush>
         </Ellipse.Stroke>
        </Ellipse>
        <Ellipse VerticalAlignment="Center" HorizontalAlignment="Center" Width="104" Height="104" StrokeThickness="3">
         <Ellipse.Stroke>
          <LinearGradientBrush>
           <LinearGradientBrush.GradientStops>
            <GradientStop Color="DarkBlue" Offset="0" />
            <GradientStop Color="LightBlue" Offset="1" />
           </LinearGradientBrush.GradientStops>
          </LinearGradientBrush>
         </Ellipse.Stroke>
         <Ellipse.Fill>
          <LinearGradientBrush EndPoint="0.96,0.972" StartPoint="0.017,0.03">
           <GradientStop Color="#FFFAF9F6" Offset="0"/>
           <GradientStop Color="#FF539BDF" Offset="1"/>
          </LinearGradientBrush>
         </Ellipse.Fill>
        </Ellipse>

        <TextBlock HorizontalAlignment="Center" Margin="0,0,0,30"  VerticalAlignment="Bottom" FontSize="10" Name="MonthDay" Text="{Binding Path=DateTime, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource weekdayConverter}}"/>

<!-- 下面这一部分有待改进,使用C#代码控制起来会更方便,且更有利于代码的维护 -->
        <Canvas Width="102" Height="102">
         <Ellipse Width="8" Height="8" Fill="Black" Canvas.Top="46" Canvas.Left="46" />
         <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="0" />
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="30" />
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="60" />
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="90" />
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="120" />
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="150" />
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="180" />
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="210" />
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="240" />
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="270" />
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="300" />
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
          <Rectangle.RenderTransform>
           <RotateTransform CenterX="2" CenterY="46" Angle="330" />
          </Rectangle.RenderTransform>
         </Rectangle>

<!-- 时钟、分针和秒针的显示 -->
         <Rectangle x:Name="HourHand" Canvas.Top="21" Canvas.Left="48"
            Fill="Black" Width="4" Height="30">
          <Rectangle.RenderTransform>
           <RotateTransform Angle="{Binding Path=DateTime, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource hoursConverter}}" CenterX="2" CenterY="30"></RotateTransform>
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle x:Name="MinuteHand" Canvas.Top="10" Canvas.Left="51"
           Fill="DarkOrange" Width="2" Height="40">
          <Rectangle.RenderTransform>
           <RotateTransform Angle="{Binding Path=DateTime, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource minutesConverter}}" CenterX="1" CenterY="40"></RotateTransform>
          </Rectangle.RenderTransform>
         </Rectangle>
         <Rectangle x:Name="SecondHand" Canvas.Top="4" Canvas.Left="49"
           Fill="Red" Width="1" Height="47">
          <Rectangle.RenderTransform>
           <RotateTransform Angle="{Binding Path=DateTime, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource secondsConverter}}" CenterX="0.5" CenterY="47"></RotateTransform>
          </Rectangle.RenderTransform>
         </Rectangle>
        </Canvas>
       </Grid>
      </ControlTemplate>
     </Setter.Value>
    </Setter>
   </Style>
  </StackPanel.Resources>
  <customControl:Clock x:Name="customControlClock" FontFamily="Arial" />
  <customControl:Clock x:Name="customControlAnalogClock" Style="{StaticResource AnalogClock}" RenderTransformOrigin="0.388,0.66" Width="121" Height="112" />
 </StackPanel>
</Window>
(已用粗体标注重要的部分)

比较一下使用GDI+和WPF的方便性,从设计的角度来讲,WPF显得更方便,更快捷。从编程的代码量相比,两者差别不是太多,我统计了一下总体代码量,GDI+甚至比WPF更少。运行效率方面,暂未做测试。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/johnsuna/article/details/1845605

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签