Introduction [^]
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.
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.
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,
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
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 ( );
offset = round ( ( double ) ( new_height - ( 4 * gap ) ) /
( double ) HEIGHT_DIVISOR );
if ( ( offset & 0x01 ) != 0 )
{
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;
UL_segment_offset [ i ].X = offset_div_2 + gap;
UL_segment_offset [ i++ ].Y = 0;
UL_segment_offset [ i ].X = 0;
UL_segment_offset [ i++ ].Y = offset_div_2 + gap;
UL_segment_offset [ i ].X = ( 5 * offset ) + ( 2 * gap );
UL_segment_offset [ i++ ].Y = offset_div_2 + gap;
UL_segment_offset [ i ].X = offset_div_2 + gap;
UL_segment_offset [ i++ ].Y = ( 5 * offset ) +
( 2 * gap );
UL_segment_offset [ i ].X = 0;
UL_segment_offset [ i++ ].Y = ( 5 * offset ) +
offset_div_2 +
( 3 * gap );
UL_segment_offset [ i ].X = ( 5 * offset ) + ( 2 * gap );
UL_segment_offset [ i++ ].Y = ( 5 * offset ) +
offset_div_2 +
( 3 * gap );
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:
int round ( double control_value )
{
return ( ( int ) ( control_value + 0.5 ) );
}
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.
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.
int [ ] [ ] segments_lit = new int [ ] [ ] {
new int [ ] { 0, 1, 2, 4, 5, 6 },
new int [ ] { 2, 5 },
new int [ ] { 0, 2, 3, 4, 6 },
new int [ ] { 0, 2, 3, 5, 6 },
new int [ ] { 1, 2, 3, 5 },
new int [ ] { 0, 1, 3, 5, 6 },
new int [ ] { 1, 3, 4, 5, 6 },
new int [ ] { 0, 2, 5 },
new int [ ] { 0, 1, 2, 3, 4, 5, 6 },
new int [ ] { 0, 1, 2, 3, 5 } };
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.
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 );
matrix.Shear ( Slant, 0.0F );
graphics.Transform = matrix;
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);
}
}
abs_digit_value = digit_value;
if ( abs_digit_value < 0 )
{
abs_digit_value = -abs_digit_value;
}
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 );
}
}
}
matrix.Shear ( 0.0F, 0.0F );
graphics.Transform = matrix;
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
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:
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.
bool [ ] segment_horizontal = new bool [
SEGMENTS_PER_DIGIT ] {
true,
false,
false,
true,
false,
false,
true };
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.
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 (
UL_corner.X,
UL_corner.Y + offset_div_2 );
points [ i++ ] = new Point (
UL_corner.X + offset_div_2,
UL_corner.Y );
points [ i++ ] = new Point (
UL_corner.X + 4 * offset + offset_div_2,
UL_corner.Y );
points [ i++ ] = new Point (
UL_corner.X + 5 * offset,
UL_corner.Y + offset_div_2 );
points [ i++ ] = new Point (
UL_corner.X + 4 * offset + offset_div_2,
UL_corner.Y + offset );
points [ i++ ] = new Point (
UL_corner.X + offset_div_2,
UL_corner.Y + offset );
points [ i++ ] = new Point (
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.
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 (
UL_corner.X,
UL_corner.Y + offset_div_2 );
points [ i++ ] = new Point (
UL_corner.X + offset_div_2,
UL_corner.Y );
points [ i++ ] = new Point (
UL_corner.X + offset,
UL_corner.Y + offset_div_2 );
points [ i++ ] = new Point (
UL_corner.X + offset,
UL_corner.Y + 4 * offset + offset_div_2 );
points [ i++ ] = new Point (
UL_corner.X + offset_div_2,
UL_corner.Y + 5 * offset );
points [ i++ ] = new Point (
UL_corner.X,
UL_corner.Y + 4 * offset + offset_div_2 );
points [ i++ ] = new Point (
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 [^]
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.
|