Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / GDI+

SevenSegmentLEDDigits Control

4.95/5 (32 votes)
6 Oct 2013CPOL9 min read 68.5K   6.1K  
Introduces a user-drawn control, named SevenSegmentLEDDigits, that displays a decimal value as a sequence of seven LED segment digits.

Introduction [^]

Seven Segment LED Digits

This article presents a revision to a control, named SevenSegmentLEDDigits. The control displays a decimal value as a sequence of digits made up of seven LED segments. The format of the display (number of digits and the presence or absence of a minus sign or decimal point) may be specified though the control's properties. At readers' requests, the following properties have been added: Slant, Gap, Border_Color, and Border_Thickness,

In the following discussions, properties that are specified by the developer are displayed in BoldMixedCase text. Variables, used internally by the software are displayed in italicized_lowercase text.

Table of Contents

The symbol [^] returns the reader to the top of the Table of Contents.

Visual Properties [^]

The SevenSegmentLEDDigits control has a number of properties that affect the user's visual image.

The developer specifies the control's top-left corner by dragging the control from the ToolBox to a position on the form. This position becomes ( 0, 0 ) in the control's graphic environment. The developer specifies the control_height by either dragging the control's resizing handles or by specifying the Control_Height property. After some computations, the values of the control_width and a number of other internal variables are set.

The LED Segment_Color defaults to Color.Red. To change the LED segment color, the developer specifies the new color through the Segment_Color property. The control's background color defaults to transparent. The control's background can be changed by specifying a Background_Color and setting Transparent to false. If Transparent is not set to false, the color specified in Background_Color will be ignored.

If a shear (slant) is desired, it is specified using the Slant property; if an inter segment gap is desired, it is specified using the Gap property; if a border is desired around the segments, the Border_Color and Border_Thickness properties are specified.

The format of the display is controlled by the Format property.

Control Properties [^]

The SevenSegmentLEDDigits control has the following properties available to the developer:

Name       Description
Background_Color Gets or sets the color of the background of the control's display. The default value is SystemColor.Control. Note that unless Transparent is set false, Background_Color will be ignored.
Border_Color Gets or sets the color of the border surrounding each segment of the control's display. The default value is Color.Black. Note that unless Border_Thickness is set to a value greater than zero, Border_Color will be ignored.
Border_Thickness Gets or sets the thickness, in pixels, of the border surrounding each segment of the control's display. The default value is zero. The value supplied for Border_Thickness must be in the closed range [ 0, 5 ]. Values outside that range will be forced into the accepted range. If a border is desired, Border_Thickness must be set to a value greater than zero.
Control_Height Gets or sets the height of the control. When changed, this property also causes other internal values for the control to be changed. The value supplied for Control_Height must be a multiple of 12 that is greater than or equal to 72. The default value is 144 pixels. When Control_Height changes, the adjust_control_dimensions_from_height method is invoked. This method will be described in detail later.
Format   Gets or sets the format of the control's display. The default value is 2. This property will be described in detail later.
Gap Gets or sets the number of pixels that will separate each segment of the control from one another. The value supplied for Gap must be in the closed range [ 0, 5 ]. Values outside that range will be forced into the accepted range. The default value is zero pixels. When the value of Gap changes, the adjust_control_dimensions_from_height method is invoked. This method will be described in detail later.
Segment_Color Gets or sets the color of the LED segments in the control's display. The default value is Color.Red.
Slant   Gets or sets the amount of shear applied to the minus sign (if any) and to each digit of the control's display. From the MSDN documentation, modified for this discussion:
The transformation is applied to the minus sign (if any) and the displayed digits. The transformation moves the bottom edge of the minus sign and digits horizontally by Slant times the height the digits.
The value supplied is a floating point number that must be in the closed range [ -0.4, 0.0 ]. Values outside that range will be forced into the accepted range. The default value is -0.1.
Transparent Gets or sets whether or not the background of the control is transparent. The default is true. Unless the value of Transparent is set to false, any supplied value of Background_Color will be ignored.
Value Gets or sets the value that will be displayed by the control.

Implementation [^]

In the following figures, Slant has the value of 0.0F.

The SevenSegmentLEDDigits control is a User-Drawn Control. Parts of the control are drawn when the control's OnPaint method is invoked. The control's graphic is made up of two distinct graphic surfaces: the background graphic and the indicator graphic. Once drawn, the background graphic does not need to be redrawn unless one or more of the following are changed:

  • Background_Color
  • Border_Thickness
  • Control_Height
  • Format
  • Gap
  • Slant
  • Transparent
  • Value

The indicator graphic must be redrawn when the background graphic is redrawn, when the Segment_Color changes, or when the value being displayed changes.

Note

The control uses Windows coordinates with the x-axis values increasing to the right and the y-axis values increasing downward.
SevenSegmentLEDDigits Control

Value Property [^]

The digits displayed in the SevenSegmentLEDDigits control are specified in the Value property. This is a signed integer value. It may contain up to four decimal digits and a prefixing minus sign. The way in which this value is displayed is specified in the Format property.

Format Property [^]

The control's indicator graphic is composed of up to four digits, a potential minus sign, and a potential decimal point. What appears in the display depends upon the content of the format string, defined as

[-]d[.[d]]

where d is a single digit in the closed range [ 0, 4 ]. The sum of both ds must be greater than zero and less than or equal to four. An ArgumentException is thrown if these constraints are not met.

The first digit of the format specifies the position of a decimal point, if, and only if, a decimal point appears in the format string. In the preceding figure, the bolded digit of each format specification is the index into an array that holds the positions of all of the possible decimal points.

Gap Property [^]

Each of the control's digits is made of up to seven segments. By default, the segments are drawn adjoining one another. If the developer wants the segments to be separated from one another, the Gap property is specified.

LED Segment Gap

The units of the Gap property are pixels. As can be seen in the figure to the left, a gap is effectively a translation of a segment's origin. Thus,

C#
S'.X = S.X + gap
S'.Y = S.Y + gap

adjust_control_dimensions_from_height Method [^]

adjust_control_dimensions_from_height is invoked when the value of any of the following changes:

  • Control_Height
  • Format
  • Gap
  • Slant
C#
// ********************* 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;
    }

The helper function zero_drawing_variables does what its name suggests and the helper function round is:

C#
// ***************************************************** round

// http://en.wikipedia.org/wiki/Rounding

int round ( double control_value )
    {

    return ( ( int ) ( control_value + 0.5 ) );
    }

LED Segment Offsets from UL_digit

Each digit in the display is composed of seven LED segments. There are horizontally and vertically aligned segments.

In the preceding code, elements of the UL_segment_offset array are assigned the offsets to each segment from UL_digit. These offsets will be eventually added to UL_digit when the digit is drawn. Thus, as shown in the figure to the left, UL_digit is the origin of each digit.

Enlarging the vertical and horizontal segments, we obtain the following figures.

Vertical Horizontal Segments

To the left is the vertical segment and to the right the horizontal segment. The coordinates of the labeled vertices will be added to appropriate offsets during the drawing operation. Note that gap was accounted for in adjust_control_dimensions_from_height

segments_lit Jagged Array [^]

The following code fragment contains the fundamental data structure used by the application.

C#
                                // 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
LED Segment Offsets from UL_digit

This jagged array contains the segment numbers that are lit (drawn) for each decimal digit. For example, the digit 7 requires that segments 0, 2, and 5 be drawn, while the digit 3 requires that segments 0, 2, 3, 5, and 6 be drawn.

These segment numbers for these two digits are displayed in the figure to the left.

This data structure replaces a complex computation with a simple lookup by a digit's decimal value.

Drawing the Digits [^]

Whenever the indicator graphics must be drawn, draw_indicator_graphic is invoked.

C#
// ************************************ 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 ( );
    }

At the line

C#
lit = segments_lit [ digit ];

the segments that must be drawn for a particular digit are retrieved from the segments_lit jagged array.

The actual location of each segment is computed as the sum of the current UL_digit and the current UL_segment_offset. The helper function add_points is simply:

C#
// ************************************************ add_points

Point add_points ( Point  P1,
                   Point  P2 )
    {

    return ( new Point ( P1.X + P2.X,
                         P1.Y + P2.Y ) );
    }

Whether a segment is vertical or horizontal is determined by accessing the appropriate element of the segment_horizontal array.

C#
bool [ ]        segment_horizontal = new bool [
                                     SEGMENTS_PER_DIGIT ] {
                                        true,            // 0
                                        false,           // 1
                                        false,           // 2
                                        true,            // 3
                                        false,           // 4
                                        false,           // 5
                                        true };          // 6

Once the location of the digit's LED segments has been computed and the segment's orientation has been determined, the segment is drawn. If the segment is horizontal, draw_horizontal_segment is invoked.

C#
// *********************************** 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 );
        }
    }

If the segment is vertical, draw_vertical_segment is invoked.

C#
// ************************************* 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 );
        }
    }

Note that draw_horizontal_segment and draw_vertical_segment are responsible for drawing segment borders.

Graphics Buffer [^]

The GraphicsBuffer class contains an off-screen bitmap used to draw graphic objects without flicker. Although .Net provides a Double Buffered Graphics capability, it is overkill for the SevenSegmentLEDDigits control.

GraphicsBuffer has been included within the control.

Demonstration [^]

Demonstration

A demonstration program that shows how the control works has been included with this article.

PowerPoint Presentation [^]

For readers who may wish to delve deeper into the algorithms used in this control, I have included a PowerPoint 2003 presentation that may be useful. For those readers who do not have PowerPoint installed, a free PowerPoint Viewer is available from Microsoft

Conclusion [^]

This article has presented a control that displays a decimal value using seven LED segments.

References [^]

Development Environment [^]

The SevenSegmentLEDDigits control was developed in the following environment:

      Microsoft Windows 7 Professional Service Pack 1
      Microsoft Visual Studio 2008 Professional
      Microsoft .Net Framework Version 3.5 SP1
      Microsoft Visual C# 2008

History [^]

09/12/2013   Original Article
09/14/2013 At the request of a reader, the Slant property has been added; the version number of the control was revised to 2.1.
09/23/2013 At the request of a reader, the Gap property has been added; the Border_Color and Border_Thickness properties have been added; the version number of the control was revised to 2.2.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)