WPF-自定义控件-指针仪表(依赖属性)
以下是学习笔记
https://www.bilibili.com/video/BV1gq4y1D76d?p=50&spm_id_from=pageDriver&vd_source=3f21d2e208ef0bf2c49a9be7560735e5
效果:

指针动画的思路:用3点画一个水平方向的指针,旋转指针的角度来实现动画。
1,新建-WPF-用户控件(WPF)
【1.1】xaml
<UserControl x:Class="JasonWPFControls.Instrument"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:JasonWPFControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Ellipse Fill="{Binding PlateBackground,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor}}" Name="backEllipse"></Ellipse>
<Canvas Name="mainCanvas" Width="{Binding Width,ElementName=backEllipse}"
Height="{Binding Height,ElementName=backEllipse}"/>
<Path Name="circle" Data="" Stroke="White" StrokeThickness="4" Width="{Binding Width,ElementName=backEllipse}"
Height="{Binding Height,ElementName=backEllipse}" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<RotateTransform Angle="-45"/>
</Path.RenderTransform>
</Path>
<!--指针-->
<Path Name="pointer" Data="" Fill="{Binding PointerBrush,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor}}" StrokeThickness="4" Width="{Binding Width,ElementName=backEllipse}"
Height="{Binding Height,ElementName=backEllipse}" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<!--实际上是改变这个角度达到动画的效果-->
<RotateTransform Angle="0" x:Name="rtPointer"/>
</Path.RenderTransform>
</Path>
<Border Width="20" Height="20" CornerRadius="10">
<Border.Background>
<RadialGradientBrush>
<GradientStop Color="White" Offset="0.583"/>
<GradientStop Color="Gray" Offset="1"/>
</RadialGradientBrush>
</Border.Background>
</Border>
</Grid>
</UserControl>
【1.2】
using System;
using System.Collections.Generic;
using System.ComponentModel;
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.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace JasonWPFControls
{
/// <summary>
/// Instrument.xaml 的交互逻辑
/// </summary>
public partial class Instrument : UserControl
{
//依赖属性,依赖对象【很重要的理念】
public int Value
{
get { return (int)GetValue(ValueProperty); }//依赖对象才有这个方法this.GetValue,普通对象没有这个方法
set { SetValue(ValueProperty, value); }//依赖对象才有这个方法this.SetValue,普通对象没有这个方法
}
//参数1,名称。参数2,什么类型的数据。参数3,这个属性属于谁
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(Instrument),
new PropertyMetadata(default(int), new PropertyChangedCallback(OnPropetyChanged)));
//propdp快捷新建依赖属性
public int MinValue
{
get { return (int)GetValue(MinValueProperty); }
set { SetValue(MinValueProperty, value); }
}
// Using a DependencyProperty as the backing store for MinValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MinValueProperty =
DependencyProperty.Register("MinValue", typeof(int), typeof(Instrument),
new PropertyMetadata(default(int), new PropertyChangedCallback(OnPropetyChanged)));
public int MaxValue
{
get { return (int)GetValue(MaxValueProperty); }
set { SetValue(MaxValueProperty, value); }
}
// Using a DependencyProperty as the backing store for MaxValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MaxValueProperty =
DependencyProperty.Register("MaxValue", typeof(int), typeof(Instrument),
new PropertyMetadata(default(int), new PropertyChangedCallback(OnPropetyChanged)));
/// <summary>
/// 分成几个大格
/// </summary>
public int Interval
{
get { return (int)GetValue(IntervalProperty); }
set { SetValue(IntervalProperty, value); }
}
// Using a DependencyProperty as the backing store for Interval. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IntervalProperty =
DependencyProperty.Register("Interval", typeof(int), typeof(Instrument),
new PropertyMetadata(default(int), new PropertyChangedCallback(OnPropetyChanged)));
/// <summary>
/// 字体大小
/// </summary>
public int ScaleTextSize
{
get { return (int)GetValue(ScaleTextSizeProperty); }
set { SetValue(ScaleTextSizeProperty, value); }
}
// Using a DependencyProperty as the backing store for ScaleTextSize. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScaleTextSizeProperty =
DependencyProperty.Register("ScaleTextSize", typeof(int), typeof(Instrument),
new PropertyMetadata(default(int), new PropertyChangedCallback(OnPropetyChanged)));
/// <summary>
/// 字体和刻度的颜色
/// </summary>
public Brush SacleBrush
{
get { return (Brush)GetValue(SacleBrushProperty); }
set { SetValue(SacleBrushProperty, value); }
}
// Using a DependencyProperty as the backing store for SacleBrush. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SacleBrushProperty =
DependencyProperty.Register("SacleBrush", typeof(Brush), typeof(Instrument),
new PropertyMetadata(default(Brush), new PropertyChangedCallback(OnPropetyChanged)));
//值发生变化,触发这个委托
public static void OnPropetyChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
{
(d as Instrument).Refresh();
}
/// <summary>
/// 底盘的背景颜色(这个属性不用触发委托方法了,在页面上绑定就可以)
/// </summary>
public Brush PlateBackground
{
get { return (Brush)GetValue(PlateBackgroundProperty); }
set { SetValue(PlateBackgroundProperty, value); }
}
// Using a DependencyProperty as the backing store for PlateBackground. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlateBackgroundProperty =
DependencyProperty.Register("PlateBackground", typeof(Brush), typeof(Instrument), new PropertyMetadata(default(Brush)));
/// <summary>
/// 指针颜色
/// </summary>
public Brush PointerBrush
{
get { return (Brush)GetValue(PointerBrushProperty); }
set { SetValue(PointerBrushProperty, value); }
}
// Using a DependencyProperty as the backing store for PointerBrush. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PointerBrushProperty =
DependencyProperty.Register("PointerBrush", typeof(Brush), typeof(Instrument), new PropertyMetadata(default(Brush)));
public Instrument()
{
InitializeComponent();
this.SizeChanged += Instrument_SizeChanged;
}
private void Instrument_SizeChanged(object sender, SizeChangedEventArgs e)
{
//设置背景高度和宽度一直,呈现圆形
double minSize = Math.Min(this.RenderSize.Width, this.RenderSize.Height);
this.backEllipse.Width = minSize;
this.backEllipse.Height = minSize;
}
private void Refresh()
{
double radius = this.backEllipse.Width / 2;
if(double.IsNaN(radius))return;
//每次绘制之前都清空
this.mainCanvas.Children.Clear();
//double min = 0, max = 100;
//几个区,几个大格
//double scaleAreaCount = 10;
//一共有多少步
double step = 270.0 / (this.MaxValue - this.MinValue);
for (int i = 0; i < this.MaxValue - this.MinValue; i++)
{
Line lineScale = new Line();
lineScale.X1 = radius - (radius - 13) * Math.Cos((i * step - 45) * Math.PI / 180);
lineScale.Y1 = radius - (radius - 13) * Math.Sin((i * step - 45) * Math.PI / 180);
lineScale.X2 = radius - (radius - 8) * Math.Cos((i * step - 45) * Math.PI / 180);
lineScale.Y2 = radius - (radius - 8) * Math.Sin((i * step - 45) * Math.PI / 180);
//线的颜色
lineScale.Stroke = this.SacleBrush;
//线的宽度
lineScale.StrokeThickness = 2;
this.mainCanvas.Children.Add(lineScale);
}
//画大刻度
step = 270.0 / this.Interval;
int scaleText = (int)this.MinValue;
for (int i = 0; i <= this.Interval; i++)
{
Line lineScale = new Line();
lineScale.X1 = radius - (radius - 20) * Math.Cos((i * step - 45) * Math.PI / 180);
lineScale.Y1 = radius - (radius - 20) * Math.Sin((i * step - 45) * Math.PI / 180);
lineScale.X2 = radius - (radius - 8) * Math.Cos((i * step - 45) * Math.PI / 180);
lineScale.Y2 = radius - (radius - 8) * Math.Sin((i * step - 45) * Math.PI / 180);
//线的颜色
lineScale.Stroke = this.SacleBrush;
//线的宽度
lineScale.StrokeThickness = 2;
this.mainCanvas.Children.Add(lineScale);
//刻度文本
TextBlock textScale = new TextBlock();
textScale.Width = 34;
textScale.TextAlignment = TextAlignment.Center;
textScale.FontSize = this.ScaleTextSize;
textScale.Text = (scaleText + (this.MaxValue - this.MinValue) / this.Interval * i).ToString();
Canvas.SetLeft(textScale, radius - (radius - 30) * Math.Cos((i * step - 45) * Math.PI / 180)-17);
Canvas.SetTop(textScale, radius - (radius - 30) * Math.Sin((i * step - 45) * Math.PI / 180)-10);
textScale.Foreground =this.SacleBrush;
this.mainCanvas.Children.Add(textScale);
}
//画圆弧
string sData = "M{0} {1} A{0} {0} 0 1 1 {1} {2}";
sData = string.Format(sData, radius / 2,radius,radius*1.5);
var convert = TypeDescriptor.GetConverter(typeof(Geometry));
this.circle.Data = (Geometry)convert.ConvertFrom(sData);
//改变指针旋转的角度达到动画效果
step = 270.0 / (this.MaxValue - this.MinValue);
//this.rtPointer.Angle = this.Value * step - 45;//到这步,更改页面“value”的值,指针会瞬间调到指定的位置
//动画过度(缓慢转动的效果)
DoubleAnimation da = new DoubleAnimation((this.Value-this.MinValue) * step - 45, new Duration(TimeSpan.FromMilliseconds(200)));
this.rtPointer.BeginAnimation(RotateTransform.AngleProperty, da);
//画指针(三角形,三点的坐标就可以画出来)
sData = "M{0} {1} ,{1} {2} , {1} {3}";
sData = string.Format(sData,radius*0.3,radius, radius-5, radius+5);
this.pointer.Data = (Geometry)convert.ConvertFrom(sData);
}
}
}
2,动画刷新的代码
public class FirstPageViewModel:NotifyBase
{
private int _instrumentValue=0;
public int InstrumentValue
{
get { return _instrumentValue; }
set { _instrumentValue = value; this.DoNotify(); }
}
Random random=new Random();
/// <summary>
/// 线程的开关
/// </summary>
private bool taskSwitch=true;
/// <summary>
/// 开启的线程集合
/// </summary>
List<Task> tasklList=new List<Task>();
public FirstPageViewModel()
{
this.RefreshInstrumentValue();
}
void RefreshInstrumentValue()
{
var task = Task.Factory.StartNew(new Action(async () =>
{
while (taskSwitch)
{
//0和100是最大值和最小值,这里为了演示效果是写死的,后期可用变量替代。
InstrumentValue = random.Next(Math.Max(this.InstrumentValue - 5,0), Math.Min( this.InstrumentValue + 5,100));
//停顿1秒刷新
await Task.Delay(1000);
}
}));
tasklList.Add(task);
}
public void Dispose()
{
try
{
taskSwitch = false;
//等待所有线程结束
Task.WaitAll(this.tasklList.ToArray());
}
catch (Exception e)
{
}
}
}
3,调用
【3.1】引入命名控件
xmlns:jasonc="clr-namespace:JasonWPFControls;assembly=JasonWPFControls"
【3.2】使用
<jasonc:Instrument Margin="0,20,0,40" Value="{Binding InstrumentValue}" MinValue="0" MaxValue="100" Interval="9"
PlateBackground="Orange" ScaleTextSize="14" SacleBrush="White" PointerBrush="Green"/>

浙公网安备 33010602011771号