SevenSegmentLEDDigits 控件






4.95/5 (32投票s)
介绍了一个名为 SevenSegmentLEDDigits 的用户绘制控件,该控件将十进制值显示为一串七段 LED 数字。
引言 [^]
 
  本文介绍了一个名为 SevenSegmentLEDDigits 的控件的修订版。该控件将十进制值显示为由七段 LED 组成的数字序列。显示格式(数字位数以及是否显示负号或小数点)可以通过控件的属性来指定。应读者要求,已添加了以下属性:倾斜 (Slant)、间距 (Gap)、边框颜色 (Border_Color) 和 边框粗细 (Border_Thickness)。
在接下来的讨论中,由开发者指定的属性将以 粗斜体混合大小写 文本显示。软件内部使用的变量将以 斜体小写 文本显示。
目录
符号 [^] 将读者带回目录的顶部。
视觉属性 [^]
SevenSegmentLEDDigits 控件有许多影响用户视觉效果的属性。
开发者通过将控件从工具箱拖放到窗体上的位置来指定控件的左上角。此位置在控件的图形环境中成为 (0, 0)。开发者通过拖动控件的调整大小手柄或指定 控件高度 (Control_Height) 属性来指定 控件高度 (control_height)。经过一些计算后,将设置 控件宽度 (control_width) 和其他内部变量的值。
LED 段颜色 (Segment_Color) 默认为 Color.Red。要更改 LED 段颜色,开发者通过 段颜色 (Segment_Color) 属性指定新颜色。控件的背景颜色默认为透明。可以通过指定 背景颜色 (Background_Color) 并将 透明 (Transparent) 设置为 false 来更改控件的背景颜色。如果 透明 (Transparent) 未设置为 false,则 背景颜色 (Background_Color) 中指定的颜色将被忽略。
如果需要倾斜(剪切),则使用 倾斜 (Slant) 属性指定;如果需要段之间的间距,则使用 间距 (Gap) 属性指定;如果需要围绕段的边框,则指定 边框颜色 (Border_Color) 和 边框粗细 (Border_Thickness) 属性。
显示格式由 格式 (Format) 属性控制。
控件属性 [^]
SevenSegmentLEDDigits 控件具有以下可供开发者使用的属性:
| 名称 | 描述 | |
| 背景颜色 (Background_Color) | 获取或设置控件显示背景的颜色。默认值为 SystemColor.Control。请注意,除非将 透明 (Transparent) 设置为 false,否则将忽略 背景颜色 (Background_Color)。 | |
| 边框颜色 (Border_Color) | 获取或设置控件显示中每个段周围边框的颜色。默认值为 Color.Black。请注意,除非将 边框粗细 (Border_Thickness) 设置为大于零的值,否则将忽略 边框颜色 (Border_Color)。 | |
| 边框粗细 (Border_Thickness) | 获取或设置控件显示中每个段周围边框的粗细(以像素为单位)。默认值为零。为 边框粗细 (Border_Thickness) 提供的值必须在闭区间 [ 0, 5 ] 内。超出此范围的值将被强制设为可接受的范围。如果需要边框,则必须将 边框粗细 (Border_Thickness) 设置为大于零的值。 | |
| Control_Height | 获取或设置控件的高度。更改时,此属性还会导致控件的其他内部值发生更改。为 控件高度 (Control_Height) 提供的值必须是 12 的倍数且大于或等于 72。默认值为 144 像素。当 控件高度 (Control_Height) 更改时,将调用 adjust_control_dimensions_from_height 方法。该方法将在 后面详细介绍。 | |
| 格式 | 获取或设置控件显示的格式。默认值为 2。该属性将在 后面详细介绍。 | |
| 间距 (Gap) | 获取或设置将分隔控件每个段的像素数。为 间距 (Gap) 提供的值必须在闭区间 [ 0, 5 ] 内。超出此范围的值将被强制设为可接受的范围。默认值为零像素。当 间距 (Gap) 的值更改时,将调用 adjust_control_dimensions_from_height 方法。该方法将在 后面详细介绍。 | |
| 段颜色 (Segment_Color) | 获取或设置控件显示中 LED 段的颜色。默认值为 Color.Red。 | |
| 倾斜 (Slant) | 获取或设置应用于负号(如果有)和控件显示中每个数字的剪切量。根据  MSDN 文档,并为本次讨论进行了修改。 变换应用于负号(如果有)和显示的数字。变换将负号和数字的底边水平移动 倾斜 (Slant) 乘以数字的高度。提供的值是浮点数,必须在闭区间 [ -0.4, 0.0 ] 内。超出此范围的值将被强制设为可接受的范围。默认值为 -0.1。 | |
| 透明 | 获取或设置控件的背景是否透明。默认值为 true。除非将 透明 (Transparent) 的值设置为 false,否则将忽略提供的任何 背景颜色 (Background_Color) 值。 | |
| 值 | 获取或设置控件将显示的值。 | 
实现 [^]
在以下图中,倾斜 (Slant) 的值为 0.0F。
SevenSegmentLEDDigits 控件是 用户绘制控件。当调用控件的 OnPaint 方法时,控件的部分内容将被绘制。控件的图形由两个不同的图形表面组成:背景图形和指示器图形。绘制完成后,背景图形无需重新绘制,除非下列一项或多项发生更改:
- 背景颜色 (Background_Color)
- 边框粗细 (Border_Thickness)
- Control_Height
- 格式
- 间距 (Gap)
- 倾斜 (Slant)
- 透明
- 值
当背景图形重绘、段颜色 (Segment_Color) 更改或显示的值更改时,必须重绘指示器图形。
注意
控件使用 Windows 坐标系,x 轴值向右增加,y 轴值向下增加。
 
  
  值 (Value) 属性 [^]
SevenSegmentLEDDigits 控件中显示的数字由 值 (Value) 属性指定。这是一个有符号整数值。它可以包含最多四个十进制数字和一个前缀负号。该值如何显示由 格式 (Format) 属性指定。
格式 (Format) 属性 [^]
控件的指示器图形由最多四个数字、一个可能的负号和一个可能的小数点组成。显示的内容取决于格式字符串的内容,定义为:
[-]d[.[d]]
其中 d 是闭区间 [ 0, 4 ] 内的一个数字。两个 d 的总和必须大于零且小于或等于四。如果未满足这些约束条件,将抛出 ArgumentException。
格式的第一个数字指定小数点的位置(仅当格式字符串中出现小数点时)。在上图中,每个格式规范中加粗的数字是存储所有可能小数点位置的数组的索引。
间距 (Gap) 属性 [^]
控件的每个数字由最多七个段组成。默认情况下,段是紧密相邻绘制的。如果开发者希望段之间有间隔,则指定 间距 (Gap) 属性。
 
  间距 (Gap) 属性的单位是像素。如左图所示,间距实际上是段原点的平移。因此,
      S'.X = S.X + gap
      S'.Y = S.Y + gap
  
  
  
  adjust_control_dimensions_from_height 方法 [^]
adjust_control_dimensions_from_height 在以下任何一项更改时被调用:
- Control_Height
- 格式
- 间距 (Gap)
- 倾斜 (Slant)
      // ********************* adjust_control_dimensions_from_height
      void adjust_control_dimensions_from_height ( int new_height )
          {
          int     decimal_point_x = 0;
          int     decimal_point_y = 0;
          int     digit_x = 0;
          int     digit_headspace = 0;
          int     i;
          int     left_shift = 0;
          double  shift = 0.0;
          zero_drawing_variables ( );
                                  // ch = 11 * o + 4 * g + 2 * o
                                  //    = 13 * o + 4 * g          =>
                                  // 13 * o = nh - 4 * g
                                  // o = ( nh - 4 * g ) / 13
          offset = round ( ( double ) ( new_height - ( 4 * gap ) ) /
                           ( double ) HEIGHT_DIVISOR );
          if ( ( offset & 0x01 ) != 0 )   // odd?
              {
              offset++;
              }
          offset_div_2 = offset / 2;
          control_height = ( HEIGHT_DIVISOR * offset ) +
                           ( 4 * gap );
          if ( ( control_height & 0x01 ) != 0 )
              {
              control_height--;
              }
          shift = ( double ) Slant;
          if ( shift < 0 )
              {
              shift = -shift;
              }
          left_shift = round ( shift * ( double ) control_height );
          decimal_point_height = offset;
          decimal_point_width = offset;
          minus_sign_height = offset;
          minus_sign_width = 3 * offset;
          UL_minus_sign.X = offset_div_2 + left_shift;
          UL_minus_sign.Y = 
              round ( ( double ) ( control_height - 
                                   minus_sign_height ) / 2.0 );
          decimal_point_x = offset_div_2 + left_shift;
          if ( have_minus_sign )
              {
              decimal_point_x += minus_sign_width;
              }
          decimal_point_y = control_height - 
                            ( 2 * offset )- 
                            offset_div_2;
          digit_x = decimal_point_x + decimal_point_width;
          digit_headspace = WIDTH_MULTIPLIER * offset + gap;
          for ( i = 0; ( i < MAXIMUM_DECIMALS ); i++ )
              {
              UL_decimal_point [ i ].X = decimal_point_x;
              UL_decimal_point [ i ].Y = decimal_point_y;
              
              decimal_point_x += digit_headspace;
              }
          for ( i = 0; ( i < MAXIMUM_DIGITS ); i++ )
              {
              UL_digit [ i ].X = digit_x;
              UL_digit [ i ].Y = offset_div_2;
              
              digit_x += digit_headspace;
              }
          control_width = offset_div_2 + left_shift;
          if ( have_minus_sign )
              {
              control_width += minus_sign_width;
              }
          control_width += decimal_point_width;
          control_width += number_digits * digit_headspace;
          control_width += offset_div_2;
          control_width += MAXIMUM_DIGITS * gap;
          i = 0;
                                                              // 0
          UL_segment_offset [ i ].X = offset_div_2 + gap;
          UL_segment_offset [ i++ ].Y = 0;
                                                              // 1
          UL_segment_offset [ i ].X = 0;
          UL_segment_offset [ i++ ].Y = offset_div_2 + gap;
                                                              // 2
          UL_segment_offset [ i ].X = ( 5 * offset ) + ( 2 * gap );
          UL_segment_offset [ i++ ].Y = offset_div_2 + gap;
                                                              // 3
          UL_segment_offset [ i ].X = offset_div_2 + gap;
          UL_segment_offset [ i++ ].Y =  ( 5 * offset ) + 
                                         ( 2 * gap );
                                                              // 4
          UL_segment_offset [ i ].X = 0;
          UL_segment_offset [ i++ ].Y = ( 5 * offset ) + 
                                        offset_div_2 + 
                                        ( 3 * gap );
                                                              // 5
          UL_segment_offset [ i ].X = ( 5 * offset ) + ( 2 * gap );
          UL_segment_offset [ i++ ].Y = ( 5 * offset ) + 
                                        offset_div_2 + 
                                        ( 3 * gap );
                                                              // 6
          UL_segment_offset [ i ].X = offset_div_2 + gap;
          UL_segment_offset [ i++ ].Y = ( 10 * offset ) + 
                                        ( 4 * gap);;
          this.Height = control_height;
          this.Width = control_width;
          }
  
  辅助函数 zero_drawing_variables 的作用顾名思义,而辅助函数 round 是:
      // ***************************************************** round
      // http://en.wikipedia.org/wiki/Rounding
      int round ( double control_value )
          {
          return ( ( int ) ( control_value + 0.5 ) );
          }
  
  
 
  显示中的每个数字由七个 LED 段组成。有水平和垂直对齐的段。
在前面的代码中,UL_segment_offset 数组的元素被赋予了从 UL_digit 到每个段的偏移量。这些偏移量最终将在绘制数字时加到 UL_digit 上。因此,如左图所示,UL_digit 是每个数字的原点。
放大水平和垂直段,我们得到以下图形。
 
  左侧是垂直段,右侧是水平段。标记顶点的坐标将在绘制操作中添加到适当的偏移量。请注意,间距已在 adjust_control_dimensions_from_height 中考虑在内。
segments_lit 锯齿数组 [^]
以下代码片段包含应用程序使用的 基本 数据结构。
                                      // declare the LED segments 
                                      // that are lit for each digit
                                      // LED segments are numbered 
                                      // from top to bottom, and 
                                      // from left to right
      int [ ] [ ]     segments_lit = new int [ ] [ ] {
                          new int [ ] { 0, 1, 2, 4, 5, 6 },    // 0
                          new int [ ] { 2, 5 },                // 1
                          new int [ ] { 0, 2, 3, 4, 6 },       // 2
                          new int [ ] { 0, 2, 3, 5, 6 },       // 3
                          new int [ ] { 1, 2, 3, 5 },          // 4
                          new int [ ] { 0, 1, 3, 5, 6 },       // 5
                          new int [ ] { 1, 3, 4, 5, 6 },       // 6
                          new int [ ] { 0, 2, 5 },             // 7
                          new int [ ] { 0, 1, 2, 3, 4, 5, 6 }, // 8
                          new int [ ] { 0, 1, 2, 3, 5 } };     // 9
  
   
  这个锯齿数组包含每个十进制数字需要点亮的(绘制的)段号。例如,数字 7 需要绘制段 0、2 和 5,而数字 3 需要绘制段 0、2、3、5 和 6。
这两个数字的段号显示在左图上。
此数据结构用简单的数字值查找代替了复杂的计算。
绘制数字 [^]
每当需要绘制指示器图形时,都会调用 draw_indicator_graphic。
      // ************************************ draw_indicator_graphic
      void draw_indicator_graphic ( Graphics graphics )
          {
          int     abs_digit_value = 0;
          int     digit = 0;
          Matrix  matrix = new Matrix ( );
          Brush   segment_brush = new SolidBrush ( segment_color );
          Pen     segment_pen = new Pen ( 
                                    border_color, 
                                    ( float ) border_thickness );
                                      // apply the shear to the 
                                      // minus sign and digits
          matrix.Shear ( Slant, 0.0F );
          graphics.Transform = matrix;
                                      // draw any minus sign
          if ( have_minus_sign && ( digit_value < 0 ) )
              {
              Rectangle rectangle = 
                  new Rectangle ( UL_minus_sign,
                                  new Size ( minus_sign_width,
                                             minus_sign_height ) );
              graphics.FillRectangle ( segment_brush, rectangle );
              if ( border_thickness > 0 )
                  {
                  graphics.DrawRectangle ( segment_pen, rectangle);
                  }
              }
                                      // already have the minus sign
                                      // so no need for negative 
                                      // value
          abs_digit_value = digit_value;
          if ( abs_digit_value < 0 )
              {
              abs_digit_value = -abs_digit_value;
              }
                                      // draw the digits from last
                                      // to first
          for ( int i = ( number_digits - 1 ); ( i >= 0 ); i-- )
              {
              int [ ]  lit;
              digit = abs_digit_value % 10;
              abs_digit_value /= 10;
              lit = segments_lit [ digit ];
              foreach ( int segment in lit )
                  {
                  Point  UL_corner;
                  UL_corner = add_points ( 
                                  UL_digit [ i ],
                                  UL_segment_offset [ segment ] );
                  if ( segment_horizontal [ segment ] )
                      {
                      draw_horizontal_segment ( UL_corner,
                                                graphics,
                                                segment_brush,
                                                segment_pen );
                      }
                  else
                      {
                      draw_vertical_segment ( UL_corner,
                                              graphics,
                                              segment_brush,
                                              segment_pen );
                      }
                  }
              }
                                      // clear the shear
          matrix.Shear ( 0.0F, 0.0F );
          graphics.Transform = matrix;
                                      // draw any decimal point
          if ( have_decimal_point )
              {
              if ( format_index >=0 )
                  {
                  Rectangle rectangle = new Rectangle (
                              UL_decimal_point [ format_index ],
                              new Size ( decimal_point_width,
                                         decimal_point_height ) );
                  graphics.FillEllipse ( segment_brush, rectangle );
                  if ( border_thickness > 0 )
                      {
                      graphics.DrawEllipse ( segment_pen, 
                                             rectangle);
                      }
                  }
              }
          segment_brush.Dispose ( );
          segment_pen.Dispose ( );
          }
  
  在以下行:
      lit = segments_lit [ digit ];
  
  需要绘制的特定数字的段从 segments_lit 锯齿数组中检索。
每个段的实际位置被计算为当前 UL_digit 和当前 UL_segment_offset 的总和。辅助函数 add_points 只是:
      // ************************************************ add_points
      Point add_points ( Point  P1,
                         Point  P2 )
          {
          return ( new Point ( P1.X + P2.X,
                               P1.Y + P2.Y ) );
          }
  
  段是垂直还是水平由访问 segment_horizontal 数组的相应元素来确定。
      bool [ ]        segment_horizontal = new bool [
                                           SEGMENTS_PER_DIGIT ] {
                                              true,            // 0
                                              false,           // 1
                                              false,           // 2
                                              true,            // 3
                                              false,           // 4
                                              false,           // 5
                                              true };          // 6
  
  一旦计算出数字 LED 段的位置并确定了段的方向,就会绘制该段。如果段是水平的,则调用 draw_horizontal_segment。
      // *********************************** draw_horizontal_segment
      void draw_horizontal_segment ( Point    UL_corner,
                                     Graphics graphics,
                                     Brush    brush,
                                     Pen      pen )
          {
          int         i = 0;
          Point [ ]   points = new Point [ SEGMENTS_PER_DIGIT ];
          i = 0;
          points [ i++ ] = new Point (                        // H1
              UL_corner.X, 
              UL_corner.Y + offset_div_2 );
          points [ i++ ] = new Point (                        // H2
              UL_corner.X + offset_div_2, 
              UL_corner.Y );
          points [ i++ ] = new Point (                        // H3
              UL_corner.X + 4 * offset + offset_div_2, 
              UL_corner.Y );
          points [ i++ ] = new Point (                        // H4
              UL_corner.X + 5 * offset, 
              UL_corner.Y + offset_div_2 );
          points [ i++ ] = new Point (                        // H5
              UL_corner.X + 4 * offset + offset_div_2, 
              UL_corner.Y + offset );
          points [ i++ ] = new Point (                        // H6
              UL_corner.X + offset_div_2, 
              UL_corner.Y + offset );
                                      // close polygon
          points [ i++ ] = new Point (                        // H1
              UL_corner.X, 
              UL_corner.Y + offset_div_2 );
          graphics.FillPolygon ( brush, points );
          if ( border_thickness > 0 )
              {
              graphics.DrawPolygon ( pen, points );
              }
          }
  
  如果段是垂直的,则调用 draw_vertical_segment。
      // ************************************* draw_vertical_segment
      void draw_vertical_segment ( Point    UL_corner,
                                   Graphics graphics,
                                   Brush    brush,
                                   Pen      pen )
          {
          int         i = 0;
          Point [ ]   points = new Point [ SEGMENTS_PER_DIGIT ];
          i = 0;
          points [ i++ ] = new Point (                        // V1
              UL_corner.X, 
              UL_corner.Y + offset_div_2 );
          points [ i++ ] = new Point (                        // V2
              UL_corner.X + offset_div_2, 
              UL_corner.Y );
          points [ i++ ] = new Point (                        // V3
              UL_corner.X + offset, 
              UL_corner.Y + offset_div_2 );
          points [ i++ ] = new Point (                        // V4
              UL_corner.X + offset, 
              UL_corner.Y + 4 * offset + offset_div_2 );
          points [ i++ ] = new Point (                        // V5
              UL_corner.X + offset_div_2, 
              UL_corner.Y + 5 * offset );
          points [ i++ ] = new Point (                        // V6
              UL_corner.X, 
              UL_corner.Y  + 4 * offset + offset_div_2 );
                                      // close polygon
          points [ i++ ] = new Point (                        // V1
              UL_corner.X, 
              UL_corner.Y + offset_div_2 );
          graphics.FillPolygon ( brush, points );
          if ( border_thickness > 0 )
              {
              graphics.DrawPolygon ( pen, points );
              }
          }
  
  请注意,draw_horizontal_segment 和 draw_vertical_segment 负责绘制段边框。
图形缓冲区 [^]
GraphicsBuffer 类包含一个脱屏位图,用于在不闪烁的情况下绘制图形对象。虽然 .Net 提供了 双缓冲图形 功能,但对于 SevenSegmentLEDDigits 控件来说,这有点大材小用。
GraphicsBuffer 已包含在控件中。
演示 [^]
 
  本文随附了一个演示程序,展示了该控件的工作原理。
PowerPoint 演示文稿 [^]
对于希望更深入了解此控件中所用算法的读者,我包含了一个可能有所帮助的 PowerPoint 2003 演示文稿。对于未安装 PowerPoint 的读者,Microsoft 提供了一个免费的 PowerPoint 查看器。
结论 [^]
本文介绍了一个使用七段 LED 显示十进制值的控件。
参考文献 [^]
- 将项目添加到工具箱
- 双缓冲图形
- FillPolygon
- 图形
- Invalidate
- 矩阵
- Matrix.Shear
- OnControlRemoved
- OnPaint
- PowerPoint 查看器
- 舍入
- 用户绘制控件
开发环境 [^]
SevenSegmentLEDDigits 控件是在以下环境中开发的:
| Microsoft Windows 7 Professional Service Pack 1 | |
| Microsoft Visual Studio 2008 Professional | |
| Microsoft .Net Framework Version 3.5 SP1 | |
| Microsoft Visual C# 2008 | 
历史 [^]
| 09/12/2013 | 原始文章 | |
| 09/14/2013 | 应读者要求,添加了 倾斜 (Slant) 属性;控件的版本号已修订为 2.1。 | |
| 09/23/2013 | 应读者要求,添加了 间距 (Gap) 属性;添加了 边框颜色 (Border_Color) 和 边框粗细 (Border_Thickness) 属性;控件的版本号已修订为 2.2。 | 


