前段时间做过一个WPF的项目,需要用到这样一个控件,在网上找了一下没找到特别合适的,要么有的不是WPF的控件(无法使用WPF的绑定功能),要么有的是WPF的但是效果不太好,或者不满足需求(比如没有小数点.或者冒号:)。所以就参考着别人的东西自己做了一个。
本文大部分属于原创内容,但是在图像绘制的时候参考(其实是照搬)了这个文章里的内容:
一 整个控件库的目录结构:
其中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_points = new List (); public List 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 ////// 设置segment的状态 /// /// /// /// /// /// /// /// /// /// 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; } ////// 根据输入字符显示相应的字符 /// /// 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方法得到各自的点集合,并存储到字典里面。
////// 初始化Segments的点集 /// private void SetSegmentsData() { dp = new DigitalParam(); dp.BevelWidth = BevelWidth; dp.SegmentInterval = SegmentInterval; dp.SegmentThickness = LEDThickness; dp.DigitalHeight = LEDHeight; dp.DigitalWidth = LEDWidth;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。
////// 画直线段 /// /// /// ///private static Path DrawLine(List 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; } /// /// 画圆点 /// /// /// /// ///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的数据,
////// 调用模板时的方法 /// 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
////// 显示值 /// /// private void DisplayData(string value ) { if (value == null) return; //准备字符 ListshowStringList = 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]; } } } /// /// 将字符串转换成可显示的单个字符的集合 /// /// ///private static List ConvertStringToSingleDigitalCharList(string value) { char[] charArray = value.ToCharArray(); List showStringList = new List (); 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; }