Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

Having fun with graphics: a VB.net GlassLabel control

Luis PérezSoftware Architect in .Net
CERTIFIED EXPERT
Software Architect in .Net C#, VB & ASP. Lover of Star Wars, MCU and Rock music. My greatest achievement in life: my 2 daughters.
Published:
A while ago, I was working on a Windows Forms application and I needed a special label control with reflection (glass) effect to show some titles in a stylish way.
The GlassLabel control at work.I've always enjoyed working with graphics, but it's never too clever to re-invent the wheel, so the first thing I did was google for a control that satisfied all my needs. Unfortunately (or not?) I didn't find anything.  I was looking for a label-like control that automatically reflects it's text contents like in a glass. Of course, reflected text must appear semi-transparent. All other control capabilities were welcome too. As I said, didn't find anything event remotely similar.

Mmmm... thinking deeply about it I realized that it would not be too difficult to build my own, so hands on!  First of all, I wrote my requirements list (and I added more and more things as soon as they popped into my head!).  The final requirement list was like this:

It must be a true label control.
It will show a reflection or glass effect of the text drawn.
The control's background can be filled with solid or gradient colors.
The control's text can be filled with solid or gradient colors, too.
The text can draw (or not) a solid-color outline with configurable width.
The level of transparency must be configurable, too.
I ended up with what you see above.  Not bad, uh?  Fortunately I can say that all goals were reached.

Once done, I thought that writing an article focusing on the graphics work for this control would be a good idea. So I will comment on what I've done and why I did it and will finish my article with the complete code listing of the control.

1. If it's a Label, then it's a Label

As said in my requirements list, the control must be a true label control. So I started creating a new class and inheriting it from the standard WinForms Label control.

Public Class GlassLabel
                          Inherits System.Windows.Forms.Label
                      End Class

Open in new window

I made two small changes to the standard behaviour of the Label control. First of all, the Label control is auto-sized by default. But for my control, the background is very important, as it admits gradients, so I decided to remove the auto-size capability of my control. This is done overriding the AutoSize property:

'The AutoSize property has been overridden to achieve the control
                      'not to be auto-sized.
                      Public Overrides Property AutoSize() As Boolean
                          Get
                              Return MyBase.AutoSize
                          End Get
                          Set(ByVal value As Boolean)
                              MyBase.AutoSize = False
                          End Set
                      End Property

Open in new window

The second change that I made was remove the possibility to place the text anywhere on the control (TextAlign), so in my control the text will be always horizontally and vertically centered.

'The TextAlign property has been overridden to achieve the control text
                      'be aligned always at Middle-Center.
                      Public Overrides Property TextAlign() As System.Drawing.ContentAlignment
                          Get
                              Return MyBase.TextAlign
                          End Get
                          Set(ByVal value As System.Drawing.ContentAlignment)
                              MyBase.TextAlign = ContentAlignment.MiddleCenter
                          End Set
                      End Property

Open in new window

This last is not really necessary, because I do all the painting over the control and so I decide exactly where the text appears. In fact, this change is useful only in the Properties Window, where you'll see always TopCenter in the TextAlign property and you will not be able to change it. But even without this piece of code the control will look exactly the same.

Finally, I have overridden completely the OnPaint method, because I don't want .Net to draw the control -- I want to do it myself with my requirements. Really, all the work is done in the OnPaint overridden method:

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
                          'All the painting work is done here.
                          ...
                      End Sub

Open in new window

2. Gradients

The control supports gradients both for the background and for the text. Gradients are one of the most easy things to do in .Net thanks to GDI+. In fact, .Net provide us with a custom brush named LinearGradientBrush and we only have to give it the rectangle in which to paint (necessary to calculate the number of steps of the gradient), the start and end colors and the direction of the gradient (horizontal, vertical or diagonal). It's as simple as this:

Dim gradBrush As LinearGradientBrush = New LinearGradientBrush(rectangle, color1, color2, direction)
                      Graphics.FillRectangle(gradBrush, rectangle)

Open in new window

Of course, you can use your gradient brush with rectangles, ellipses, polygons, regions, paths, etc.
In the complete listing of GlassLabel control you'll find some code in which I use gradients, and you'll see how easy is to use them.

3. Outline

There are some methods to draw text into a Graphics object. The most basic of all is using the DrawString method, which takes some parameters (text, font, size, location...) and draws the text using the brush that you want.

But for the outline requirement I needed more functionality than DrawString offers. So I used GraphicsPath object. As MSDN says, GraphicsPath represents a series of connected lines and curves. Applications use paths to draw outlines of shapes, fill the interiors of shapes, and create clipping regions. Best of all, the GraphicsPath object provides with it's own AddString method, so we just need to call AddString on the GraphicsPath and it creates a path that outlines the string passed. After that, you can both draw the string outline or fill its contents.

Dim path As GraphicsPath = New GraphicsPath
                      path.AddString(text, fontFamily, fontStyle, emSize, clippingRect, stringFormat)
                      Graphics.DrawPath(pen, path)
                      Graphics.FillPath(brush, path)

Open in new window

In the complete listing of GlassLabel control you'll find some code in which I use GraphicsPath, and you'll see how to use them.

4. Reflection

As may be obvious, the more difficult of the requirements was the glass or reflection effect. As with almost all in GDI+, there are many ways to do this. But I think that I've used maybe the simplest of all.

A graphics object supports a series of transformations that can affect to his size, rotation angle, etc. Once one of this transformation is applied to a graphics object, all the painting work done over the object is affected by the transformations done. This transformations can be cumulative and that gives us an extremely powerful mechanism to work with graphics.

In this case I did a really simple transformation: I "said" to my graphics object that all the painting must be affected by a scale transformation. The scale applied is 1 for the horizontal axis (so no transformation) and -1 for the vertical axis (so no transformation in size but vertically reflected). It's as simple as that. You'll find the complete sample in the code listing at the end of the article.
Graphics.ScaleTransform(1, -1)

Open in new window

After this point, all that I paint over this graphics objects (text, shapes, etc) will appear vertically reflected.

The Complete Listing

Well, believe it or not, with the use of these few GDI+ objects, all can be done.  Here is my complete listing code for the GlassLabel control. The code is well commented and I think that you can follow it easily.

As a suggestion for further work, maybe it would be a good idea to not override the TextAlign property and make it work in the control as expected, re-calculating the position for each one of the possible values.  Another interesting challenge would be to find a way to show the reflection effect in perspective.
Imports System.Drawing
                      Imports System.Drawing.Drawing2D
                      Imports System.Drawing.Imaging
                      
                      Public Class GlassLabel
                          Inherits System.Windows.Forms.Label
                      
                          'Variables to hold properties values
                          'Each initialization values represent the default value for the property
                          Private m_BackGradientColor1 As Color = Color.Black
                          Private m_BackGradientColor2 As Color = Color.White
                          Private m_BackGradientMode As LinearGradientMode = LinearGradientMode.Horizontal
                          Private m_BackGradient As Boolean = False
                          Private m_Alpha As Integer = 100
                          Private m_ForeGradientColor1 As Color = Color.Purple
                          Private m_ForeGradientColor2 As Color = Color.White
                          Private m_ForeGradientMode As LinearGradientMode = LinearGradientMode.Vertical
                          Private m_ForeGradient As Boolean = False
                          Private m_OffsetY As Integer = 0
                          Private m_OutlineColor As Color = Color.White
                          Private m_OutlineWidth As Integer = 0
                      
                          'This property get/set text outline border color
                          Public Property OutlineColor() As Color
                              Get
                                  Return m_OutlineColor
                              End Get
                              Set(ByVal value As Color)
                                  m_OutlineColor = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'This property get/set text outline border width
                          'If set to zero, then it's not outline
                          Public Property OutlineWidth() As Integer
                              Get
                                  Return m_OutlineWidth
                              End Get
                              Set(ByVal value As Integer)
                                  m_OutlineWidth = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'This property get/set an integer value that represents
                          'the number of pixels that must close up the normal text and
                          'the reflected text. As MeasureString considers special characters
                          'and glyphos, it returns vertically an extra space. The OffsetY
                          'property tells the control to dispose N pixels of space between
                          'the drawn texts.
                          Public Property OffsetY() As Integer
                              Get
                                  Return m_OffsetY
                              End Get
                              Set(ByVal value As Integer)
                                  m_OffsetY = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'This property get/set a value indicating if draw the texts
                          'using a gradient fill (true) or a solid fill (false)
                          Public Property ForeGradient() As Boolean
                              Get
                                  Return m_ForeGradient
                              End Get
                              Set(ByVal value As Boolean)
                                  m_ForeGradient = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'This property get/set a value indicating the direction of
                          'the texts gradient fill
                          Public Property ForeGradientMode() As LinearGradientMode
                              Get
                                  Return m_ForeGradientMode
                              End Get
                              Set(ByVal value As LinearGradientMode)
                                  m_ForeGradientMode = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'This property get/set a value indicating the second color of
                          'the texts gradient fill
                          Public Property ForeGradientColor2() As Color
                              Get
                                  Return m_ForeGradientColor2
                              End Get
                              Set(ByVal value As Color)
                                  m_ForeGradientColor2 = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'This property get/set a value indicating the first color of
                          'the texts gradient fill
                          Public Property ForeGradientColor1() As Color
                              Get
                                  Return m_ForeGradientColor1
                              End Get
                              Set(ByVal value As Color)
                                  m_ForeGradientColor1 = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'This property get/set a value that represents the level of
                          'transparency in the reflected text. Values must be between 0 and 255.
                          'The lower value, reflected text is more transparent.
                          'The higher value, more opaque.
                          Public Property Alpha() As Integer
                              Get
                                  Return m_Alpha
                              End Get
                              Set(ByVal value As Integer)
                                  m_Alpha = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'This property get/set a value indicating if draw the control's background
                          'using a gradient fill (true) or a solid fill (false)
                          Public Property BackGradient() As Boolean
                              Get
                                  Return m_BackGradient
                              End Get
                              Set(ByVal value As Boolean)
                                  m_BackGradient = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'This property get/set a value indicating the direction of
                          'the control's background gradient fill
                          Public Property BackGradientMode() As LinearGradientMode
                              Get
                                  Return m_BackGradientMode
                              End Get
                              Set(ByVal value As LinearGradientMode)
                                  m_BackGradientMode = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'This property get/set a value indicating the second color of
                          'the control's background gradient fill
                          Public Property BackGradientColor2() As Color
                              Get
                                  Return m_BackGradientColor2
                              End Get
                              Set(ByVal value As Color)
                                  m_BackGradientColor2 = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'This property get/set a value indicating the first color of
                          'the control's background gradient fill
                          Public Property BackGradientColor1() As Color
                              Get
                                  Return m_BackGradientColor1
                              End Get
                              Set(ByVal value As Color)
                                  m_BackGradientColor1 = value
                                  Me.Invalidate()
                              End Set
                          End Property
                      
                          'The AutoSize property has been overridden to achieve the control
                          'not to be auto-sized.
                          Public Overrides Property AutoSize() As Boolean
                              Get
                                  Return MyBase.AutoSize
                              End Get
                              Set(ByVal value As Boolean)
                                  MyBase.AutoSize = False
                              End Set
                          End Property
                      
                          'The TextAlign property has been overridden to achieve the control text
                          'be aligned always at Middle-Center.
                          Public Overrides Property TextAlign() As System.Drawing.ContentAlignment
                              Get
                                  Return MyBase.TextAlign
                              End Get
                              Set(ByVal value As System.Drawing.ContentAlignment)
                                  MyBase.TextAlign = ContentAlignment.MiddleCenter
                              End Set
                          End Property
                      
                          'All the paintint work is done here.
                          Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
                              With e.Graphics
                                  '*** Apply high-quality properties to the graphics object ************************
                                  .CompositingQuality = CompositingQuality.HighQuality
                                  .InterpolationMode = InterpolationMode.HighQualityBicubic
                                  .PixelOffsetMode = PixelOffsetMode.HighQuality
                                  .SmoothingMode = SmoothingMode.HighQuality
                                  .TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAlias
                      
                                  '*** Draw the control's background ***********************************************
                                  If Me.BackGradient Then
                                      'The background must show a gradient, so we need to create a gradient brush
                                      'and fill the control's background rectangle with this gradient
                                      Using bkg As LinearGradientBrush = New LinearGradientBrush(e.ClipRectangle, Me.BackGradientColor1, Me.BackGradientColor2, Me.BackGradientMode)
                                          .FillRectangle(bkg, e.ClipRectangle)
                                      End Using
                                  Else
                                      'The background is a solid color. The Clear method of the Graphics object
                                      'let us to choose a color to clear the background.
                                      .Clear(Me.BackColor)
                                  End If
                                  '*********************************************************************************
                      
                                  '*** Create and draw the normal (not reflected) text *****************************
                                  'Get the text width & height
                                  Dim width As Single = .MeasureString(Me.Text, Me.Font).Width
                                  Dim height As Single = .MeasureString(Me.Text, Me.Font).Height
                      
                                  'Create a rectangle that delimites the position & size of the text drawn
                      
                                  'The x position must be (control width - text width) / 2, so the text will be
                                  'horizontally centered
                                  Dim xpos As Single = (e.ClipRectangle.Width - width) / 2
                      
                                  'The y position must be also vertically centered, so we start from
                                  '(control height - text height) / 2
                                  'But below the normal text will be the reflected text, so must offset to top
                                  'the half of text height
                                  'Additionally, MeasureString give us extra space reserved for tall glyphos,
                                  'so must consider the OffsetY value to delete this extra space, so must offset
                                  'to bottom the half of OffsetY value
                                  Dim ypos As Single = ((e.ClipRectangle.Height - height) / 2) - (height / 2) + (Me.OffsetY / 2)
                      
                                  'Finally, create the rectangle from x,y pos and width & height of the text
                                  Dim originalRect As New RectangleF(xpos, ypos, width, height)
                      
                                  'Draw the original string. We'll use a GraphicsPath object instead
                                  'using DrawString directly, because GraphicsPath will let us draw an
                                  'outline border to the text
                      
                                  'Create the path
                                  Dim path As GraphicsPath = New GraphicsPath
                      
                                  'Add the string to the path. Because GraphicsPath's AddString method
                                  'uses emSize (the height of the em square box that bounds the character)
                                  'instead of Point, we must convert out font's Point size to emSize using
                                  'this formula: (Vertical Resolution / 72) * Font's Point Size
                                  path.AddString(Me.Text, Me.Font.FontFamily, Me.Font.Style, (.DpiY / 72) * Me.Font.Size, originalRect, StringFormat.GenericDefault)
                      
                                  'If and outline must be drawn, draw it
                                  If Me.OutlineWidth > 0 Then
                                      Using p As Pen = New Pen(Me.OutlineColor, Me.OutlineWidth)
                                          .DrawPath(p, path)
                                      End Using
                                  End If
                      
                                  'Create the brush to fill the text
                                  Dim fill As Brush
                                  If Me.ForeGradient Then
                                      'Text must be filled with a gradient brush
                                      fill = New LinearGradientBrush(originalRect, Me.ForeGradientColor1, Me.ForeGradientColor2, Me.ForeGradientMode)
                                  Else
                                      'Text must be filled with a solid brush
                                      fill = New SolidBrush(Me.ForeColor)
                                  End If
                      
                                  'Fill the text and destroy the brush
                                  .FillPath(fill, path)
                                  fill.Dispose()
                      
                                  'The GraphicsPath object won't be needed anymore
                                  path.Dispose()
                      
                                  'From this point we must deal with reflected text. So it's a good idea to
                                  'save the current state of our graphics object. What is really saved is the
                                  'state of the objects (transformations applied, etc), not the drawings done
                                  'until here.
                                  Dim state As GraphicsState = .Save
                      
                                  'Reset the transformations done until here so we start from a "fresh clean"
                                  'graphics object state.
                                  .ResetTransform()
                      
                                  'ScaleTransform will set the graphics object into a state in which all the
                                  'drawings done after the instruction will be affected by the scaling done.
                                  'As we use 1 for horizontal value, the drawings will be not changed in the
                                  'horizontal plane. But as we use -1 for the vertical value, all the drawings
                                  'will be vertically inverted (the reflection effect that we want).
                                  .ScaleTransform(1, -1)
                      
                                  'Now, as we did for the normal text, we'll create a rectangle that delimites
                                  'the position and size of the reflected text
                      
                                  'The x-position must not be changed, as it is the same (horizontally centered)
                      
                                  'The y-pos must be vertically centered, so we start from
                                  '(control height - text height) / 2
                                  'From there, as we did with normal text, we must offset the half of the text height
                                  '(in this case, offset to top)
                                  'Also must offset the OffsetY value, to top too
                                  'BUT we must remember that this will be drawn over a transformed (Scaled)
                                  'graphics object, so must invert all signs (for example, offset to bottom instead
                                  'to top)
                                  ypos = (((((e.ClipRectangle.Height - height) / 2) + (height / 2)) * -1) - height) + (Me.OffsetY / 2)
                      
                                  'Create the rectangle
                                  Dim reflectedRect As New RectangleF(xpos, ypos, width, height)
                      
                                  'Create the path to hold the text
                                  Dim reflectedPath As GraphicsPath = New GraphicsPath
                      
                                  'Add the string to the path
                                  reflectedPath.AddString(Me.Text, Me.Font.FontFamily, Me.Font.Style, (.DpiY / 72) * Me.Font.Size, reflectedRect, StringFormat.GenericDefault)
                      
                                  'Draw the outline, if it applies
                                  If Me.OutlineWidth > 0 Then
                                      'Note that we apply alpha transparency to the outline. If not, reflected
                                      'text's outline will appear too much "solid"
                                      Using p As Pen = New Pen(Color.FromArgb(Me.Alpha, Me.OutlineColor), Me.OutlineWidth)
                                          .DrawPath(p, reflectedPath)
                                      End Using
                                  End If
                      
                                  'Create the brush to fill the reflected text
                                  If Me.ForeGradient Then
                                      'We must apply Alpha transparency on both gradient colors
                                      fill = New LinearGradientBrush(reflectedRect, Color.FromArgb(Me.Alpha, Me.ForeGradientColor1), Color.FromArgb(Me.Alpha, Me.ForeGradientColor2), Me.ForeGradientMode)
                                  Else
                                      'Apply Alpha to solid color too
                                      fill = New SolidBrush(Color.FromArgb(Me.Alpha, Me.ForeColor))
                                  End If
                      
                                  'Draw the text (it will be automatically reflected because of the Scale
                                  'transformation applied)
                                  .FillPath(fill, reflectedPath)
                      
                                  'Destroy objects that are no more needed
                                  fill.Dispose()
                                  reflectedPath.Dispose()
                      
                                  'Restore the Graphics object state (eliminate transformations, so if we drew
                                  'anymore from here will not be reflected)
                                  .Restore(state)
                                  '*********************************************************************************
                              End With
                          End Sub
                      End Class
                      

Open in new window

I hope that you find this control to be useful.  Feel free to use it in your applications and modify to your convenience.
6
11,314 Views
Luis PérezSoftware Architect in .Net
CERTIFIED EXPERT
Software Architect in .Net C#, VB & ASP. Lover of Star Wars, MCU and Rock music. My greatest achievement in life: my 2 daughters.

Comments (3)

Mike TomlinsonHigh School Computer Science, Computer Applications, Digital Design, and Mathematics Teacher
CERTIFIED EXPERT
Top Expert 2009

Commented:
Beautiful control with a well written article and nice, clean code.

Thanks for sharing Roland!  =)

~IM
Luis PérezSoftware Architect in .Net
CERTIFIED EXPERT

Author

Commented:
Thank you very much, Idle_Mind.
hi thanks for your share,, then how could i use it in my form?

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.