WPF自定义LED风格数字显示控件

简介: 原文:WPF自定义LED风格数字显示控件 版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/article/details/52895167 ...
原文: WPF自定义LED风格数字显示控件

版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/article/details/52895167

       前段时间做过一个WPF的项目,需要用到这样一个控件,在网上找了一下没找到特别合适的,要么有的不是WPF的控件(无法使用WPF的绑定功能),要么有的是WPF的但是效果不太好,或者不满足需求(比如没有小数点.或者冒号:)。所以就参考着别人的东西自己做了一个。

       本文大部分属于原创内容,但是在图像绘制的时候参考(其实是照搬)了这个文章里的内容:A Fine-looking Segmented LED Control

     

      一 整个控件库的目录结构:


      


       其中DigitalControl.cs为单个LED数字控件,DigitalPanelControl.cs为多个DigitalControl的组合控件,Segment目录下的类为数字的7条边和冒号以及小数点的类。Data目录下的DigitalData类存放一个DigitalControl的Segment数据,而DigitalParam存放单个DigitalControl的基本属性,包括宽,高,线条粗细以及间距等。


       控件构造的基本思路为:通过单个控件的基本属性参数(宽、高、粗细等)——也就是DigitalParam类中的数据——去构造组成单个数字的10个Segment,然后通过组合Segment得到DigitalControl,再通过组合DigitControl得到DigitalPanelControl,普通的控件使用者主要是使用DigitalPanelControl。


       二 Segment类 


      


        ===========================


      


       Segment类为数字的7条边(如上图)以及冒号(:)的两个点和小数点,一共10个子类,他们继承共同的父类Segments,由一个或多个点构成的Path组成,每个Segment的画法如上图所示。

       (注:上图照搬自 这里 )

       Segment类为一个抽象父类,类中定义了一个List<Point>列表,用来存放构成Segment的点,然后声明了一个GetPoints的虚方法,在其子类中复写了此方法,主要用于通过给定的DigitalParam数据得到Segment所需要的点(填充_points)。


       下面是Segment父类和其中一个子类TopSegment的代码。

    public abstract class Segment
    {
        private List<Point> _points = new List<Point>();

        public List<Point> Points
        {
            get { return _points; }
            set { _points = value; }
        }

        public abstract void GetPoints(DigitalParam dp);
    }


    public class TopSegment : Segment
    {
        public TopSegment(DigitalParam dp)
        {
            GetPoints(dp);
        }

        public override void GetPoints(DigitalParam dp)
        {
            Points.Add(new Point(dp.BevelWidth * 2 + dp.SegmentInterval, 0));
            Points.Add(new Point(dp.DigitalWidth - dp.BevelWidth * 2 - dp.SegmentInterval, 0));
            Points.Add(new Point(dp.DigitalWidth - dp.BevelWidth - dp.SegmentInterval, dp.BevelWidth));
            Points.Add(new Point(dp.DigitalWidth - dp.SegmentInterval - dp.SegmentThickness, dp.SegmentThickness));
            Points.Add(new Point(dp.SegmentThickness + dp.SegmentInterval, dp.SegmentThickness));
            Points.Add(new Point(dp.BevelWidth + dp.SegmentInterval, dp.BevelWidth));
        }
    }

      

       三 DigitalData类


       该类为单个数字控件的数据类,主要包括10个支持数字显示的Segment的字段和属性,以及两个控制数字显示内容的方法。其中DisplayDigital根据给定的数字字符去调用LightDigitalSegments方法,通过LightDigitalSegments方法设置不同Segment的显示或隐藏来显示具体的数字。


    public class DigitalData
    {
        #region Fields

        private Path topSegment;
        private Path upLeftSegment;
        private Path upRightSegment;
        private Path middleSegment;
        private Path downLeftSegment;
        private Path downRightSegment;
        private Path bottomSegment;

        private Path dotSegment;
        private Path colonUpSegment;
        private Path colonDownSegment;

        #endregion

        #region Properties

        public Path TopSegment
        {
            get
            {
                return topSegment;
            }

            set
            {
                topSegment = value;
            }
        }

        public Path UpLeftSegment
        {
            get
            {
                return upLeftSegment;
            }

            set
            {
                upLeftSegment = value;
            }
        }

        public Path UpRightSegment
        {
            get
            {
                return upRightSegment;
            }

            set
            {
                upRightSegment = value;
            }
        }

        public Path MiddleSegment
        {
            get
            {
                return middleSegment;
            }

            set
            {
                middleSegment = value;
            }
        }

        public Path DownLeftSegment
        {
            get
            {
                return downLeftSegment;
            }

            set
            {
                downLeftSegment = value;
            }
        }

        public Path DownRightSegment
        {
            get
            {
                return downRightSegment;
            }

            set
            {
                downRightSegment = value;
            }
        }

        public Path BottomSegment
        {
            get
            {
                return bottomSegment;
            }

            set
            {
                bottomSegment = value;
            }
        }

        public Path DotSegment
        {
            get
            {
                return dotSegment;
            }

            set
            {
                dotSegment = value;
            }
        }

        public Path UpColonSegment
        {
            get
            {
                return colonUpSegment;
            }

            set
            {
                colonUpSegment = value;
            }
        }

        public Path DownColonSegment
        {
            get
            {
                return colonDownSegment;
            }

            set
            {
                colonDownSegment = value;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// 设置segment的状态
        /// </summary>
        /// <param name="top"></param>
        /// <param name="upRight"></param>
        /// <param name="downRight"></param>
        /// <param name="bottom"></param>
        /// <param name="downLeft"></param>
        /// <param name="upLeft"></param>
        /// <param name="middle"></param>
        /// <param name="dot"></param>
        /// <param name="colon"></param>
        private void LightDigitalSegments(bool top, bool upRight, bool downRight, bool bottom,
            bool downLeft, bool upLeft, bool middle, bool dot, bool colon)
        {
            double ON = 1;
            double OFF = 0.05;

            TopSegment.Opacity = top ? ON : OFF;
            UpRightSegment.Opacity = upRight ? ON : OFF;
            DownRightSegment.Opacity = downRight ? ON : OFF;
            BottomSegment.Opacity = bottom ? ON : OFF;
            DownLeftSegment.Opacity = downLeft ? ON : OFF;
            UpLeftSegment.Opacity = upLeft ? ON : OFF;
            MiddleSegment.Opacity = middle ? ON : OFF;
            DotSegment.Opacity = dot ? ON : OFF;
            UpColonSegment.Opacity = DownColonSegment.Opacity = colon ? ON : OFF;           
        }

        /// <summary>
        /// 根据输入字符显示相应的字符
        /// </summary>
        /// <param name="digital"></param>
        public void DisplayDigital(string digital)
        {
            switch (digital)
            {
                case null:
                case " ":
                    LightDigitalSegments(false, false, false, false, false, false, false, false, false);
                    break;
                case "0":
                    LightDigitalSegments(true, true, true, true, true, true, false, false, false);
                    break;
                case "1":
                    LightDigitalSegments(false, true, true, false, false, false, false, false, false);
                    break;
                case "2":
                    LightDigitalSegments(true, true, false, true, true, false, true, false, false);
                    break;
                case "3":
                    LightDigitalSegments(true, true, true, true, false, false, true, false, false);
                    break;
                case "4":
                    LightDigitalSegments(false, true, true, false, false, true, true, false, false);
                    break;
                case "5":
                    LightDigitalSegments(true, false, true, true, false, true, true, false, false);
                    break;
                case "6":
                    LightDigitalSegments(true, false, true, true, true, true, true, false, false);
                    break;
                case "7":
                    LightDigitalSegments(true, true, true, false, false, false, false, false, false);
                    break;
                case "8":
                    LightDigitalSegments(true, true, true, true, true, true, true, false, false);
                    break;
                case "9":
                    LightDigitalSegments(true, true, true, true, false, true, true, false, false);
                    break;
                case "0.":
                    LightDigitalSegments(true, true, true, true, true, true, false, true, false);
                    break;
                case "1.":
                    LightDigitalSegments(false, true, true, false, false, false, false, true, false);
                    break;
                case "2.":
                    LightDigitalSegments(true, true, false, true, true, false, true, true, false);
                    break;
                case "3.":
                    LightDigitalSegments(true, true, true, true, false, false, true, true, false);
                    break;
                case "4.":
                    LightDigitalSegments(false, true, true, false, false, true, true, true, false);
                    break;
                case "5.":
                    LightDigitalSegments(true, false, true, true, false, true, true, true, false);
                    break;
                case "6.":
                    LightDigitalSegments(true, false, true, true, true, true, true, true, false);
                    break;
                case "7.":
                    LightDigitalSegments(true, true, true, false, false, false, false, true, false);
                    break;
                case "8.":
                    LightDigitalSegments(true, true, true, true, true, true, true, true, false);
                    break;
                case "9.":
                    LightDigitalSegments(true, true, true, true, false, true, true, true, false);
                    break;
                case ":":
                case ":":
                    LightDigitalSegments(false, false, false, false, false, false, false, false, true);
                    break;
                case "-":
                    LightDigitalSegments(false, false, false, false, false, false, true, false, false);
                    break;
                default:
                    throw new Exception("输入字符错误!");
            }
        }

        #endregion
    }


       四 DigitalControl类

      

       单个数字控件的类,继承自ContentControl

    public class DigitalControl : ContentControl

       类里面定义了一些和控件显示相关的依赖属性,如控制颜色的LEDColorProperty,控制segment粗细的LEDThicknessProperty,控制显示内容的ValueProperty等等,并为这些依赖属性定义了属性包装器和属性变化回调方法。


       控件在显示前会调用OnApplyTemplate方法,会进行控件的初始化,包括调用SetSegmentsData方法得到所有Segment的点集,然后调用DrwaSegments绘制所有的Segment,然后将这些Segment都添加到控件的根容器中,最终设置其显示的值。


    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        //获取根布局
        rootGrid = GetTemplateChild("gdRoot") as Grid;

        //初始化Segments的点集digitalSegmentDict
        SetSegmentsData();
        //画数字
        dd = DrawSegments(digitalSegmentDict, LEDColor);

        //将线段添加到容器
        AddSegmentsToPanel(dd);

        dd.DisplayDigital(Value);
    }


       SetSegmentsData方法首先初始化DigitalParam数据,然后根据DigitalParam通过Segment子类实例化时调用各自的GetPoints方法得到各自的点集合,并存储到字典里面。


    /// <summary>
    /// 初始化Segments的点集
    /// </summary>
    private void SetSegmentsData()
    {
        dp = new DigitalParam();
        dp.BevelWidth = BevelWidth;
        dp.SegmentInterval = SegmentInterval;
        dp.SegmentThickness = LEDThickness;
        dp.DigitalHeight = LEDHeight;
        dp.DigitalWidth = LEDWidth;<pre class="csharp" name="code">        digitalSegmentDict["TopSegment"] = new TopSegment(dp);
        digitalSegmentDict["UpRightSegment"] = new UpRightSegment(dp);
        digitalSegmentDict["DownRightSegment"] = new DownRightSegment(dp);
        digitalSegmentDict["BottomSegment"] = new BottomSegment(dp);
        digitalSegmentDict["DownLeftSegment"] = new DownLeftSegment(dp);
        digitalSegmentDict["UpLeftSegment"] = new UpLeftSegment(dp);
        digitalSegmentDict["MiddleSegment"] = new MiddleSegment(dp);
        digitalSegmentDict["UpColonSegment"] = new UpColonSegment(dp);
        digitalSegmentDict["DownColonSegment"] = new DownColonSegment(dp);
        digitalSegmentDict["DotSegment"] = new DotSegment(dp);
    }
 
 


       DrawSegments会根据参数的Point个数调用DrawLine和DrawEllipse方法来绘制图像得到DigitalData。


    /// <summary>
    /// 画直线段
    /// </summary>
    /// <param name="points"></param>
    /// <param name="clr"></param>
    /// <returns></returns>
    private static Path DrawLine(List<Point> points, Color clr)
    {
        PathSegmentCollection segments = new PathSegmentCollection();
        for (int i = 1; i < points.Count; i++)
        {
            segments.Add(new LineSegment(points[i], true));
        }

        Path segment = new Path()
        {
            StrokeLineJoin = PenLineJoin.Round,
            Stroke = new SolidColorBrush(clr),
            Fill = new SolidColorBrush(clr),
            Opacity = 0.05,
            StrokeThickness = 0.25,
            Data = new PathGeometry()
            {
                Figures = new PathFigureCollection()
                {
                    new PathFigure(){IsClosed = true, IsFilled = true, StartPoint = points[0], Segments = segments}
                }
            }
       };
        return segment;
    }

    /// <summary>
    /// 画圆点
    /// </summary>
    /// <param name="p"></param>
    /// <param name="radius"></param>
    /// <param name="clr"></param>
    /// <returns></returns>
    private Path DrawEllipse(Point p, double radius, Color clr)
    {
        Color strokecolor;
        if (clr == Colors.Transparent)
        {
            strokecolor = clr;
        }
        else
        {
            strokecolor = Colors.White;
        }

        Path segment = new Path()
        {
            StrokeLineJoin = PenLineJoin.Round,
            Stroke = new SolidColorBrush(strokecolor),

            Fill = new SolidColorBrush(clr),
            Opacity = 0.05,
            StrokeThickness = 0.25,
            Data = new EllipseGeometry(p, radius, radius)
        };
            
        return segment;
    }


       五 DigitalPanelControl类


       先在Generic.xaml中设置控件风格,病将背景颜色、宽、高绑定为TemplatedParent的数据,

    <Style TargetType="{x:Type local:DigitalPanelControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:DigitalPanelControl}">
                    <StackPanel x:Name="LayoutRoot" Orientation="Horizontal"
                         Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}"
                         Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Width}"
                         Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

       同样在该类中定义了一系列的依赖属性用于控件的数据绑定,包括Digital的个数,控件的宽、高,背景色、前景色等等,同时定义了一系列属性改变回调方法和属性包装器。

        在OnApplyTemplate方法中,会根据当前的DigitalCount数据添加相应个数的DigitalContral到根容器,然后调用DisplayData方法显示数据

        /// <summary>
        /// 调用模板时的方法
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            //获取根布局
            rootPanel = GetTemplateChild("LayoutRoot") as StackPanel;

            //添加Led
            DrawDigitals(DigitalCount);

            //将Digitals 加入到rootPanel中
            foreach(DigitalControl digital in digitalsList)
            {
                rootPanel.Children.Add(digital);
            }

            DisplayData(Value);
        }

       DisplayData方法会先将所要显示的字符串转换成单个DigitalControl可显示的字符列表,然后根据DigitalPanelControl的DigitalControl的个数和所要显示的数据的长度,来决定显示的方式,数据过长则截断尾数,数据少于digital的个数,则填充左边digital


        /// <summary>
        /// 显示值
        /// </summary>
        /// <param name="value"></param>
        private void DisplayData(string value )
        {
            if (value == null)
                return;

            //准备字符
            List<string> showStringList = ConvertStringToSingleDigitalCharList(value);

            //显示文字
            if (digitalsList.Count < showStringList.Count)//要显示的字符个数大于显示器的个数,截断尾数
            {
                for (int i = 0; i < digitalsList.Count; i++)
                {
                    digitalsList[i].Value = showStringList[i];
                }
            }
            else//否则从显示器的低位开始填充
            {
                for(int i = 0; i < digitalsList.Count - showStringList.Count; i++)
                {
                    digitalsList[i].Value = null;
                }
                for(int i = digitalsList.Count - showStringList.Count; i < digitalsList.Count; i++)
                {
                    digitalsList[i].Value = showStringList[i - digitalsList.Count + showStringList.Count];
                }
            }
        }

        /// <summary>
        /// 将字符串转换成可显示的单个字符的集合
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static List<string> ConvertStringToSingleDigitalCharList(string value)
        {
            char[] charArray = value.ToCharArray();
            List<string> showStringList = new List<string>();
            for (int i = 0; i < charArray.Length; i++)
            {
                if (i == charArray.Length - 1)//最后一位数字,直接复制
                {
                    showStringList.Add(charArray[i].ToString());
                    break;
                }
                if (charArray[i] >= '0' && charArray[i] <= '9' && charArray[i + 1] == '.')//带小数点数字,将小数点与数字放到同一个字符串里
                {
                    showStringList.Add(charArray[i] + ".");
                    i++;
                }
                else
                {
                    showStringList.Add(charArray[i].ToString());
                }
            }

            return showStringList;
        }


       最终的效果图:


      


源代码

目录
相关文章
|
6月前
|
C# 开发者 Windows
基于Material Design风格开源、易用、强大的WPF UI控件库
基于Material Design风格开源、易用、强大的WPF UI控件库
389 0
|
6月前
|
C#
浅谈WPF之装饰器实现控件锚点
使用过visio的都知道,在绘制流程图时,当选择或鼠标移动到控件时,都会在控件的四周出现锚点,以便于修改大小,移动位置,或连接线等,那此功能是如何实现的呢?在WPF开发中,想要在控件四周实现锚点,可以通过装饰器来实现,今天通过一个简单的小例子,简述如何在WPF开发中,应用装饰器,仅供学习分享使用,如有不足之处,还请指正。
142 1
|
3月前
|
开发框架 缓存 前端开发
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
|
3月前
|
C# 开发者 Windows
一款基于Fluent设计风格、现代化的WPF UI控件库
一款基于Fluent设计风格、现代化的WPF UI控件库
|
3月前
|
C# Windows
WPF中如何使用HandyCotrol控件库
WPF中如何使用HandyCotrol控件库
195 1
|
3月前
|
C# 前端开发 UED
WPF数据验证实战:内置控件与自定义规则,带你玩转前端数据验证,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,数据验证是确保输入正确性的关键环节。前端验证能及时发现错误,提升用户体验和程序可靠性。本文对比了几种常用的WPF数据验证方法,并通过示例展示了如何使用内置验证控件(如`TextBox`)及自定义验证规则实现有效验证。内置控件结合`Validation`类可快速实现简单验证;自定义规则则提供了更灵活的复杂逻辑支持。希望本文能帮助开发者更好地进行WPF数据验证。
118 0
|
3月前
|
C# UED 定位技术
WPF控件大全:初学者必读,掌握控件使用技巧,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,控件是实现用户界面交互的关键元素。WPF提供了丰富的控件库,包括基础控件(如`Button`、`TextBox`)、布局控件(如`StackPanel`、`Grid`)、数据绑定控件(如`ListBox`、`DataGrid`)等。本文将介绍这些控件的基本分类及使用技巧,并通过示例代码展示如何在项目中应用。合理选择控件并利用布局控件和数据绑定功能,可以提升用户体验和程序性能。
69 0
|
3月前
|
开发框架 前端开发 JavaScript
WPF应用开发之控件动态内容展示
WPF应用开发之控件动态内容展示
|
3月前
|
开发框架 前端开发 JavaScript
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件
|
3月前
|
C#
WPF 自定义可拖动标题栏
WPF 自定义可拖动标题栏
51 0