其他分享
首页 > 其他分享> > WPF-自定义控件-指针仪表(依赖属性)

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"/>

 

 

 

标签:控件,自定义,DependencyProperty,int,System,using,new,WPF,public
来源: https://www.cnblogs.com/baozi789654/p/16426045.html