WPF 时钟示例
一.首先添加一个自定义控件 Clock
导入两个引用:Microsoft.Expression.Controls.dll 和 Microsoft.Expression.Drawing.dll
Generic.xaml代码:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPF0316a"
xmlns:ec="http://schemas.microsoft.com/expression/2010/controls"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing">
<Style x:Key="temp" TargetType="{x:Type local:Clock}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="UseLayoutRounding" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Clock}">
<Border Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
UseLayoutRounding="{TemplateBinding UseLayoutRounding}">
<Grid>
<ed:Arc x:Name="PART_IncreaseCircle" ArcThickness="3" ArcThicknessUnit="Pixel" EndAngle="360"
Fill="#424658" StartAngle="0" Stretch="None" />
<ec:PathListBox x:Name="ShoartTick" IsHitTestVisible="False"
ItemsSource="{TemplateBinding ShortTicks}">
<ec:PathListBox.ItemTemplate>
<DataTemplate>
<Border Width="1" Height="7" Background="#3B4053" SnapsToDevicePixels="True"
UseLayoutRounding="True" />
</DataTemplate>
</ec:PathListBox.ItemTemplate>
<ec:PathListBox.LayoutPaths>
<ec:LayoutPath Distribution="Even" Orientation="OrientToPath"
SourceElement="{Binding ElementName=ShortTickPath}" />
</ec:PathListBox.LayoutPaths>
</ec:PathListBox>
<ec:PathListBox x:Name="LongTick" IsHitTestVisible="False"
ItemsSource="{TemplateBinding LongTicks}">
<ec:PathListBox.ItemTemplate>
<DataTemplate>
<Border Width="3" Height="3" Background="White" CornerRadius="100"
SnapsToDevicePixels="True" UseLayoutRounding="True" />
</DataTemplate>
</ec:PathListBox.ItemTemplate>
<ec:PathListBox.LayoutPaths>
<ec:LayoutPath Distribution="Even" Orientation="OrientToPath"
SourceElement="{Binding ElementName=LongTickPath}" />
</ec:PathListBox.LayoutPaths>
</ec:PathListBox>
<ec:PathListBox x:Name="Number" IsHitTestVisible="False"
ItemsSource="{TemplateBinding NumberList}">
<ec:PathListBox.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="White" RenderTransformOrigin="0.5, 0.5"
Text="{Binding Item1}">
<TextBlock.RenderTransform>
<RotateTransform Angle="{Binding Item2}" />
</TextBlock.RenderTransform>
</TextBlock>
</DataTemplate>
</ec:PathListBox.ItemTemplate>
<ec:PathListBox.LayoutPaths>
<ec:LayoutPath Distribution="Even" Orientation="OrientToPath"
SourceElement="{Binding ElementName=NumberPath}" />
</ec:PathListBox.LayoutPaths>
</ec:PathListBox>
<ed:Arc x:Name="LongTickPath" Margin="2" ArcThickness="0" ArcThicknessUnit="Pixel"
EndAngle="0" StartAngle="30" Stretch="None" />
<ed:Arc x:Name="ShortTickPath" Margin="5" ArcThickness="0" ArcThicknessUnit="Pixel"
EndAngle="360" StartAngle="0" Stretch="None" />
<ed:Arc x:Name="NumberPath" Margin="20" ArcThickness="0" ArcThicknessUnit="Pixel"
EndAngle="0" StartAngle="30" Stretch="None" />
<ed:Arc x:Name="PART_SecondCircle" ArcThickness="3" ArcThicknessUnit="Pixel" Fill="#FFF"
StartAngle="0" Stretch="None" />
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:Clock}">
<Setter Property="Width" Value="200" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="UseLayoutRounding" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Clock}">
<Viewbox Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<Border Width="200" Height="200" CornerRadius="1000"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
UseLayoutRounding="{TemplateBinding UseLayoutRounding}">
<Grid>
<ed:Arc x:Name="PART_IncreaseCircle" ArcThickness="3" ArcThicknessUnit="Pixel" EndAngle="360"
Fill="#424658" StartAngle="0" Stretch="None" />
<ed:Arc ArcThickness="1" ArcThicknessUnit="Pixel" EndAngle="360"
Fill="#424658" StartAngle="0" Stretch="None" Margin="5" />
<ec:PathListBox x:Name="ShoartTick" IsHitTestVisible="False"
ItemsSource="{TemplateBinding ShortTicks}">
<ec:PathListBox.ItemTemplate>
<DataTemplate>
<Border Width="2" Height="2" Background="#000" CornerRadius="100"
SnapsToDevicePixels="True" UseLayoutRounding="True" />
</DataTemplate>
</ec:PathListBox.ItemTemplate>
<ec:PathListBox.LayoutPaths>
<ec:LayoutPath Distribution="Even" Orientation="OrientToPath"
SourceElement="{Binding ElementName=ShortTickPath}" />
</ec:PathListBox.LayoutPaths>
</ec:PathListBox>
<ec:PathListBox x:Name="LongTick" IsHitTestVisible="False"
ItemsSource="{TemplateBinding LongTicks}">
<ec:PathListBox.ItemTemplate>
<DataTemplate>
<Border Width="4" Height="4" Background="#000" CornerRadius="100"
SnapsToDevicePixels="True" UseLayoutRounding="True" />
</DataTemplate>
</ec:PathListBox.ItemTemplate>
<ec:PathListBox.LayoutPaths>
<ec:LayoutPath Distribution="Even" Orientation="OrientToPath"
SourceElement="{Binding ElementName=LongTickPath}" />
</ec:PathListBox.LayoutPaths>
</ec:PathListBox>
<ec:PathListBox x:Name="Number" IsHitTestVisible="False"
ItemsSource="{TemplateBinding NumberList}">
<ec:PathListBox.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="Black" RenderTransformOrigin="0.5, 0.5"
Text="{Binding Item1}">
<TextBlock.RenderTransform>
<RotateTransform Angle="{Binding Item2}" />
</TextBlock.RenderTransform>
</TextBlock>
</DataTemplate>
</ec:PathListBox.ItemTemplate>
<ec:PathListBox.LayoutPaths>
<ec:LayoutPath Distribution="Even" Orientation="OrientToPath"
SourceElement="{Binding ElementName=NumberPath}" />
</ec:PathListBox.LayoutPaths>
</ec:PathListBox>
<ed:Arc x:Name="LongTickPath" Margin="12" ArcThickness="0" ArcThicknessUnit="Pixel"
EndAngle="0" StartAngle="30" Stretch="None" />
<ed:Arc x:Name="ShortTickPath" Margin="13" ArcThickness="0" ArcThicknessUnit="Pixel"
EndAngle="360" StartAngle="0" Stretch="None" />
<ed:Arc x:Name="NumberPath" Margin="30" ArcThickness="0" ArcThicknessUnit="Pixel"
EndAngle="0" StartAngle="30" Stretch="None" />
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<Border Width="3" Height="50" Background="Black" RenderTransformOrigin="0.5 1">
<Border.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding HourAngleInner, RelativeSource={RelativeSource AncestorType=local:Clock}}" CenterY="0" />
<TranslateTransform Y="-25" />
</TransformGroup>
</Border.RenderTransform>
</Border>
<Border Width="2" Height="60" Background="Blue" RenderTransformOrigin="0.5 1">
<Border.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding MinuteAngleInner, RelativeSource={RelativeSource AncestorType=local:Clock}}" CenterY="0" />
<TranslateTransform Y="-30" />
</TransformGroup>
</Border.RenderTransform>
</Border>
<Border Width="2" Height="80" Background="Red" RenderTransformOrigin="0.5, 1">
<Border.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding SecondAngleInner, RelativeSource={RelativeSource AncestorType=local:Clock}}" CenterY="0" />
<TranslateTransform Y="-40" />
</TransformGroup>
</Border.RenderTransform>
</Border>
<Border Width="7" Height="7" BorderBrush="Black" Background="White" BorderThickness="2" CornerRadius="100" />
</Grid>
</Border>
</Viewbox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Clock.cs代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace WPF0316a
{
public class Clock : ContentControl
{
#region private fields
private DispatcherTimer mSecondTimer;
#endregion
#region DependencyProperty
#region Hour
public string Hour
{
get { return (string)GetValue(HourProperty); }
set { SetValue(HourProperty, value); }
}
public static readonly DependencyProperty HourProperty =
DependencyProperty.Register("Hour", typeof(string), typeof(Clock));
#endregion
#region Minute
public string Minute
{
get { return (string)GetValue(MinuteProperty); }
set { SetValue(MinuteProperty, value); }
}
public static readonly DependencyProperty MinuteProperty =
DependencyProperty.Register("Minute", typeof(string), typeof(Clock));
#endregion
#region Second
public string Second
{
get { return (string)GetValue(SecondProperty); }
set { SetValue(SecondProperty, value); }
}
public static readonly DependencyProperty SecondProperty =
DependencyProperty.Register("Second", typeof(string), typeof(Clock));
#endregion
#endregion
#region Private DependencyProperty
#region ShortTicks 短刻度线集合
/// <summary>
/// 短刻度线依赖属性,用于Binding
/// </summary>
public static readonly DependencyProperty ShortTicksProperty =
DependencyProperty.Register(
"ShortTicks",
typeof(IList<object>),
typeof(Clock),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置短刻度线,用于绑定PathListBox的ItemsSource
/// </summary>
/// <value>短刻度线.</value>
public IList<object> ShortTicks
{
get { return (IList<object>)GetValue(ShortTicksProperty); }
private set { SetValue(ShortTicksProperty, value); }
}
#endregion
#region LongTicks 长刻度线集合
/// <summary>
/// 长刻度线依赖属性,用于Binding
/// </summary>
public static readonly DependencyProperty LongTicksProperty =
DependencyProperty.Register(
"LongTicks",
typeof(IList<object>),
typeof(Clock),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置长刻度线,用于绑定PathListBox的ItemsSource
/// </summary>
/// <value>长刻度线.</value>
public IList<object> LongTicks
{
get { return (IList<object>)GetValue(LongTicksProperty); }
private set { SetValue(LongTicksProperty, value); }
}
#endregion
#region NumberList 长刻度线上显示的数字
/// <summary>
/// 长刻度线依赖属性,用于Binding
/// </summary>
public static readonly DependencyProperty NumberListProperty =
DependencyProperty.Register(
"NumberList",
typeof(IList<Tuple<object, double>>),
typeof(Clock),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置长刻度线,用于绑定PathListBox的ItemsSource
/// </summary>
/// <value>长刻度线.</value>
public IList<Tuple<object, double>> NumberList
{
get { return (IList<Tuple<object, double>>)GetValue(NumberListProperty); }
private set { SetValue(NumberListProperty, value); }
}
#endregion
#region HourAngleInner
public double HourAngleInner
{
get { return (double)GetValue(HourAngleInnerProperty); }
private set { SetValue(HourAngleInnerProperty, value); }
}
public static readonly DependencyProperty HourAngleInnerProperty =
DependencyProperty.Register("HourAngleInner", typeof(double), typeof(Clock), new PropertyMetadata(0d));
#endregion
#region MinuteAngleInner
public double MinuteAngleInner
{
get { return (double)GetValue(MinuteAngleInnerProperty); }
private set { SetValue(MinuteAngleInnerProperty, value); }
}
public static readonly DependencyProperty MinuteAngleInnerProperty =
DependencyProperty.Register("MinuteAngleInner", typeof(double), typeof(Clock), new PropertyMetadata(0d));
#endregion
#region SecondAngleInner
public double SecondAngleInner
{
get { return (double)GetValue(SecondAngleInnerProperty); }
private set { SetValue(SecondAngleInnerProperty, value); }
}
public static readonly DependencyProperty SecondAngleInnerProperty =
DependencyProperty.Register("SecondAngleInner", typeof(double), typeof(Clock), new PropertyMetadata(0d));
#endregion
#endregion
#region Constructors
static Clock()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Clock), new FrameworkPropertyMetadata(typeof(Clock)));
}
#endregion
#region Override
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SetTicks();
if (this.mSecondTimer == null)
{
this.mSecondTimer = new DispatcherTimer();
this.mSecondTimer.Interval = new TimeSpan(0, 0, 0, 1); ;
this.mSecondTimer.Tick += MSecondTimer_Tick; ;
}
//int millisecond = DateTime.Now.Millisecond;
//System.Threading.Thread.Sleep(1000 - millisecond);
this.mSecondTimer.Start();
this.SetAngle();
}
#endregion
#region private function
private void SetTicks()
{
List<Tuple<object, double>> numbers = new List<Tuple<object, double>>();
List<object> shortticks = new List<object>();
List<object> longticks = new List<object>();
for (int i = 1; i <= 12; i++)
{
double angle = -(360 / 12); //一圈360度,分12个时钟刻度
numbers.Add(new Tuple<object, double>(i, i * angle));
longticks.Add(new object());
}
for (int i = 0; i < 60; i++)
{
shortticks.Add(new object());
}
this.ShortTicks = shortticks;
this.LongTicks = longticks;
this.NumberList = numbers;
}
private void SetAngle()
{
int hour = DateTime.Now.Hour;
int minute = DateTime.Now.Minute;
int second = DateTime.Now.Second;
if (hour == 0)
{
this.HourAngleInner = 360;
}
else
{
this.HourAngleInner = hour % 12 * 30 + (double)minute / 60 * 30;
}
if (minute == 0)
{
this.MinuteAngleInner = 360;
}
else
{
this.MinuteAngleInner = minute * 6;
}
if (second == 0)
{
this.SecondAngleInner = 360;
}
else
{
this.SecondAngleInner = second * 6;
}
this.Hour = hour >= 10 ? hour.ToString() : "0" + hour;
this.Minute = minute >= 10 ? minute.ToString() : "0" + minute;
this.Second = second >= 10 ? second.ToString() : "0" + second;
}
#endregion
#region Event Implement Function
private void MSecondTimer_Tick(object sender, EventArgs e)
{
this.SetAngle();
}
#endregion
}
}
主窗体Xaml代码:
<Window x:Class="WPF0316a.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPF0316a"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:Clock x:Name="clock" Padding="0" Background="AntiqueWhite">
<local:Clock.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="60">
<TextBlock FontFamily="Agency FB" FontSize="16" Foreground="Black"
Text="{Binding Hour, ElementName=clock}" />
<TextBlock FontFamily="Agency FB" FontSize="16" Foreground="Black" Text=":" />
<TextBlock FontFamily="Agency FB" FontSize="16" Foreground="Black"
Text="{Binding Minute, ElementName=clock}" />
<TextBlock FontFamily="Agency FB" FontSize="16" Foreground="Black" Text=":" />
<TextBlock FontFamily="Agency FB" FontSize="16" Foreground="Black"
Text="{Binding Second, ElementName=clock}" />
</StackPanel>
</DataTemplate>
</local:Clock.ContentTemplate>
</local:Clock>
</Grid>
</Window>
运行效果:
源代码库下载地址:https://download.csdn.net/download/qq_43024228/12251111