Introduction
I had been working on a custom label control that had a built-in shadow, so I didn't have to layer separate labels or draw text multiple times to a Graphics
object. After reading Wong Shao Voon's outstanding article Outline Text [^], I knew I found exactly what I needed. The simplicity of just layering ever widening Pens to get the glow effect was great. I would love to implement more of his ideas someday, but haven't had time to convert it to VB.NET yet. Someday...
As usual with me, what was a simple control to fill a need in the beginning kept growing as I thought of all the what if's. The static outline/shadow label gains a clickable mouseover option which expands to a pulsing alternative to the marquee progress bar...
What is the gLabel
? In simple terms, it is an extended Label
or in other words, a custom control that inherits Label
and overrides the OnPaint
event to give it a brand new paint job.
Features:
- Solid or blended fill color
- Outline text
- Outer glow highlighting
MouseOver
glow color - Pulsing glow option
- Text shadow
- AutoFit
- CheckBox and RadioButton behavior option
Control Properties
Here is a list of the primary properties:
If True
, the text to the bounds of the control; False
uses font size to paint text
Handle the appearance of the background box area
Use solid color or a custom blend of colors
What type of gradient fill to apply
Color or Colors to paint the actual text
How far to extend the glow color out from the text
What color to Glow if the GlowState
is true
If FeatherSate
is True
, fade the glow color
How intense to make the glow color
If MouseOver
is true
, what color to paint the glow
If Pulse
is true
, fade the GlowColor
in and out
If ShadowState
is true
, paint a shadow to the offset
AutoFit
BackColor, Border, BorderColor, BorderWidth
FillType
FillTypeLinear
ForeColor
, ForeColorBlend
Glow
GlowColor
, GlowState
FeatherState
Feather
MouseOver
, MouseOverColor
Pulse
, PulseSpeed
ShadowState
, ShadowColor
, ShadowOffset
UITypeEditor
ForeColorBlend
uses the custom BlendTypeEditor
. See UITypeEditorsDemo[^] for a detailed explanation of UITypeEditor
s and ControlDesigner
s.
Using the gLabel
Drop a gLabel
on your Form just like a normal Label
. AutoSize is hidden, so size the control to fit the FontSize
or set AutoFit
to True
to make the text fit the control.
Points of Interest
First the transparent background behaves just like a standard Label
control. I tried using the true transparent background method with Protected Overrides ReadOnly Property CreateParams()
, but the flickering was horrendous, so I removed it.
On to what does work. As with any control, the painting consists of a series of layers. The first of course is the background. This takes place in the Protected Overrides Sub OnPaintBackground
event. If BackColor
is Transparent
, then let the MyBase
handle it. Otherwise I get the background color according to its Enabled
state.
Protected Overrides Sub OnPaintBackground( _
ByVal pevent As System.Windows.Forms.PaintEventArgs)
If Me.BackColor = Color.Transparent Then
MyBase.OnPaintBackground(pevent)
Else
pevent.Graphics.Clear(EnabledColor(Me.BackColor))
End If
End Sub
The rest of the layers are in the Protected Overrides Sub OnPaint
event and will paint based on the state properties for each.
Before I start on the paint layers, let me explain the Glow process first since it is used more than once here.
Glow
The Glow is a gradient that follows the outline of the letters. The gradient is dark close to the letter's edge and gets lighter as it gets further away. In the Color
structure ARGB, the A is the Alpha value or how much you can see through it. The value ranges from 0 to 255, with 0 being totally transparent and 255 being a solid color you cannot see through.
If you paint a square in one color like Color.FromARGB(125, Color.Red)
and then overlap part of it with another square painted in Color.FromARGB(125, Color.Blue)
, the parts that do not overlap will be a light red or blue. The parts that do overlap will mix to become purple and more opaque. Now do the same using the same color for both squares. The overlap is blue and the other parts are light blue and more translucent.
Now let's look at 6 blue rectangles, each one wider than the last. When you line up the left edges, it makes a gradient like blend from dark to light.
Take this one step further by making each successive rectangle a little more transparent than the last and it looks even better.
Use this idea with a Pen
, where the width of the rectangle corresponds to the width of a Pen
. Using the GraphicsPath
of a String
, loop the number of times you want to create the glow effect. Then fill the path at the end to create the face of the text.
For i As Integer = 1 To _Glow Step 2
Dim aGlow As Integer = CInt(_Feather - _
((_Feather / _Glow) * i))
Using pen As Pen = New Pen(Color.FromArgb( _
aGlow, EnabledColor(gColor)), i)
pen.LineJoin = LineJoin.Round
g.DrawPath(pen, path)
End Using
Next i
g.FillPath(New SolidBrush(Me.ForeColor), path)
OnPaint
OK, back to the layers. First, to paint the shadow, I get a GraphicsPath
for the text and use a Matrix
to Translate
the (X, Y) coordinates by the ShadowOffset
property. Then paint the shadow using the above Glow process.
Next is the actual glow text. Determine the glow color based on the MouseOver
properties. Use the above Glow process, but with these options: FeatherState
, Enabled
, and Pulse
. Lastly, fill the text path using the fill properties.
The last layer is the border which simply draws a rectangle around the gLabel
's rectangle area using the border properties.
AutoFit
The standard label has an AutoSize
property, but all it does is adjust the width to the text length. I wanted the text to fill the area. To fit a text path to the size of the control, I use a Matrix
to transform the GraphicsPath
.
Let's look at when you need to fit a label within a specific area. Long text in a standard Label
will get cut off. Reducing the Font.Size
to make the text fit sometimes makes the height too small to read well. Fitting the text squeezes the width independently of the height.
To do this, first make a Rectangle
of the area you want the text to fit inside.
Dim target As New Rectangle( _
0, 0, _
Me.ClientSize.Width, _
Me.ClientSize.Height)
Then get a PointF
representation of the target rectangle.
Dim target_pts() As PointF = { _
New PointF(target.Left, target.Top), _
New PointF(target.Right, target.Top), _
New PointF(target.Left, target.Bottom) _
}
Use the GraphicsPath AddString
to get a path of the text close to the size of the target rectangle. Then use GetBounds
to get the rectangle around the path.
path.AddString(Me.Text, Me.Font.FontFamily, Me.Font.Style, _
target.Height, New PointF(0, 0), sf)
Dim text_rectf As RectangleF = path.GetBounds()
Then use a Matrix
to Transform
the Graphics
object to fit the text to the target rectangle.
g.Transform = New Matrix(text_rectf, target_pts)
To pulse the GlowColor
, a Timer
is used to increase and decrease the Feather
value.
Private Sub Pulser_Tick(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Pulser.Tick
_PulseAdj += PulseDirection
If _Feather - _PulseAdj < 10 _
OrElse _Feather - _PulseAdj > _Feather Then
PulseDirection *= -1
_PulseAdj += PulseDirection
End If
Me.Invalidate()
End Sub
TypeConverter for ColorFillBlend
New in Version 1.0.8, a TypeConverter
for the ForeColorBlend
can convert a String
representation of the blend to a cBlendItems
to allow more precise tweaking and to easily copy a blend from one gLabel
to another.
CheckType: CheckBox and RadioButton Behaviors
The CheckType
property can be set as Label
, Check
, or Radio
. Label
behaves like a standard label when clicked. Check
sets the Checked
property and colors independent of the other gLabel
s when clicked. Radio
sets the Checked
property to True
and sets all other gLabel
s set to type Radio
to False
on the same parent.
When the GlowMatchChecked
property is True
, the GlowState
will change with the Checked
property.
Fancy label done.
History
- Version 1.0.5 - July 2010
- Version 1.0.6 - January 2011
- Added padding support
- Added
TextWordWrap
property - Added AutoEllispis support
- Version 1.0.7 - March 2011
- Added
MouseOverForeColor
property - Added
Checked
property - Added
CheckedColor
property - Added CheckedToggleState method
- Version 1.0.8 - January 2012
- Added
BlendItemsConverter
- Cleaned up default Values
- Added
CheckedType
property