在软件开发中,操作系统提供的原生控件通常是最符合用户习惯的选择。它们不仅易于使用,而且用户已经熟悉了这些控件的操作方式,因此,除非在极端情况下,通常不需要创建自定义控件。然而,有时候,某些控件的设计让人忍不住想要尝试编写它们,哪怕它们并非那么“实用”。
这就是决定编写这个七段LED控制的原因:它看起来非常酷,而且通过编写这个控制,能够更熟悉C#和.NET的内部机制。如果喜欢这个控制,并且能够使用它,或者从中学到东西,那就更好了。
即使以前没有听说过“七段显示”这个名字,也可能在很多电子设备上看到过它,比如微波炉上的计时器、CD播放器上的显示屏,或者数字手表上的时间显示。它们之所以被称为七段显示,是因为它们由七个“段”组成——七个单独的灯(LED或其他类型)以不同的模式点亮,代表0到9的任何一个数字。
要将这个自定义控制集成到应用程序中,只需将"SevenSegment.cs"文件包含到项目中。重新构建项目,将能够在工具面板中选择SevenSegment控制,并将其直接拖放到表单上。
为了模仿七段显示的外观,绘制了七个多边形,精确匹配真实显示的物理布局。为了模拟这些多边形,在坐标纸上绘制了它们,并记录了每个多边形的每个点的坐标。在控制上绘制多边形时,使用了FillPolygon函数,传递给它代表多边形的点数组。让来检查一下控制的Paint事件,看看到底发生了什么:
private void SevenSegment_Paint(object sender, PaintEventArgs e)
{
// 这将是显示在段上的位模式,
// 位0到6对应每个段。
int useValue = customPattern;
// 创建代表点亮和未点亮状态的画刷
Brush brushLight = new SolidBrush(colorLight);
Brush brushDark = new SolidBrush(colorDark);
// 定义容器的变换...
RectangleF srcRect = new RectangleF(0.0F, 0.0F, gridWidth, gridHeight);
RectangleF destRect = new RectangleF(Padding.Left, Padding.Top, this.Width - Padding.Left - Padding.Right, this.Height - Padding.Top - Padding.Bottom);
// 开始图形容器,重新映射坐标,以方便
GraphicsContainer containerState = e.Graphics.BeginContainer(destRect, srcRect, GraphicsUnit.Pixel);
// 根据“斜体”系数应用剪切变换
Matrix trans = new Matrix();
trans.Shear(italicFactor, 0.0F);
e.Graphics.Transform = trans;
// 应用抗锯齿
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.PixelOffsetMode = PixelOffsetMode.Default;
// 根据相应位是高还是低来绘制元素!
// "segPoints"是一个二维数组的点,包含要绘制的段坐标
e.Graphics.FillPolygon((useValue & 0x1) == 0x1 ? brushLight : brushDark, segPoints[0]);
e.Graphics.FillPolygon((useValue & 0x2) == 0x2 ? brushLight : brushDark, segPoints[1]);
e.Graphics.FillPolygon((useValue & 0x4) == 0x4 ? brushLight : brushDark, segPoints[2]);
e.Graphics.FillPolygon((useValue & 0x8) == 0x8 ? brushLight : brushDark, segPoints[3]);
e.Graphics.FillPolygon((useValue & 0x10) == 0x10 ? brushLight : brushDark, segPoints[4]);
e.Graphics.FillPolygon((useValue & 0x20) == 0x20 ? brushLight : brushDark, segPoints[5]);
e.Graphics.FillPolygon((useValue & 0x40) == 0x40 ? brushLight : brushDark, segPoints[6]);
// 如果启用了,绘制小数点
if (showDot)
e.Graphics.FillEllipse(dotOn ? brushLight : brushDark, gridWidth - 1, gridHeight - elementWidth + 1, elementWidth, elementWidth);
// 完成坐标容器
e.Graphics.EndContainer(containerState);
}
可以通过两个属性来设置控制中显示的值:Value和CustomPattern。Value属性是一个字符串值,可以设置为单个字符,如"5"或"A"。字符将自动转换为看起来像指定字符的七段位模式。
如果想显示一个自定义模式,它可能看起来像也可能不像任何字母或数字,可以使用CustomPattern属性,并将其设置为0到127之间的任何值,这让完全控制每个段,因为位0到6控制每个相应段的状态。
在代码中,有一个枚举,编码所有预定义的值,这些值代表可以在七段上显示的数字和字母:
public enum ValuePattern
{
None = 0x0, Zero = 0x77, One = 0x24, Two = 0x5D, Three = 0x6D,
Four = 0x2E, Five = 0x6B, Six = 0x7B, Seven = 0x25,
Eight = 0x7F, Nine = 0x6F, A = 0x3F, B = 0x7A, C = 0x53,
D = 0x7C, E = 0x5B, F = 0x1B, G = 0x73, H = 0x3E,
J = 0x74, L = 0x52, N = 0x38, O = 0x78,
P = 0x1F, Q = 0x2F, R = 0x18,
T = 0x5A, U = 0x76, Y = 0x6E,
Dash = 0x8, Equals = 0x48
}
注意,每个值都是一个位图,每个位对应一个七段中的一个段。现在,在Value属性的setter中,将给定的字符与已知值进行比较,并使用相应的枚举作为当前显示的位模式:
// 是数字吗?
int tempValue = Convert.ToInt32(value);
switch (tempValue)
{
case 0: customPattern = (int)ValuePattern.Zero; break;
case 1: customPattern = (int)ValuePattern.One; break;
...
}
...
// 是字母吗?
string tempString = Convert.ToString(value);
switch (tempString.ToLower()[0])
{
case 'a': customPattern = (int)ValuePattern.A; break;
case 'b': customPattern = (int)ValuePattern.B; break;
...
}
无论如何,要显示的位模式最终都会在customPattern变量中,然后在Paint事件中如上所示使用。
还可以“斜体化”显示,通过操纵ItalicFactor属性。这个值只是一个剪切因子,在绘制控制时应用,如在Paint事件中所见。一个-0.1的斜体因子会使显示看起来只是稍微倾斜,而且看起来更专业。
如果开始注意到段被绘制在控制的边界之外(可能是由于太多的斜体化),可以使用Padding属性,并增加左/右/上/下的填充,直到所有的形状都在控制的客户矩形内。
控制还有几个其他方便的属性供玩耍,比如背景颜色、段的启用和禁用颜色,以及段的厚度。
除了七段控制本身,还提供了另一个控制,即七段显示的数组。这允许在一系列7段显示上显示整个字符串。查看演示应用程序,并深入源代码看看它是怎么用的;真的很简单。
要使用数组控制,将"SevenSegmentArray.cs"文件包含到项目中并重新构建。然后将能够从工具面板中选择SevenSegmentArray控制。
这个控制有一个ArrayCount属性,指定数组中的7段显示的数量,以及一个Value属性,它接受要在数组上显示的任何字符串。简单吧?
必须说,编写这个控制非常有趣,.NET通过使绘制自己的形状、转换坐标和引入真正强大的属性变得非常容易,增加了很多乐趣。
另外,由于有一点电子背景,对来说,看到这个控制带来了对更简单时代的某种怀旧。希望能喜欢它。