<

Go Premium for a chance to win a PS4. Enter to Win

x

Having fun with graphics: a VB.net GlassLabel control

Published on
18,551 Points
8,451 Views
6 Endorsements
Last Modified:
Awarded
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
Comment
Author:Luis Pérez
3 Comments
 
LVL 86

Expert Comment

by:Mike Tomlinson
Beautiful control with a well written article and nice, clean code.

Thanks for sharing Roland!  =)

~IM
0
 
LVL 25

Author Comment

by:Luis Pérez
Thank you very much, Idle_Mind.
0
 

Expert Comment

by:Elazreg mm
hi thanks for your share,, then how could i use it in my form?
0

Featured Post

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Join & Write a Comment

Is your data getting by on basic protection measures? In today’s climate of debilitating malware and ransomware—like WannaCry—that may not be enough. You need to establish more than basics, like a recovery plan that protects both data and endpoints.…
In a question here at Experts Exchange (https://www.experts-exchange.com/questions/29062564/Adobe-acrobat-reader-DC.html), a member asked how to create a signature in Adobe Acrobat Reader DC (the free Reader product, not the paid, full Acrobat produ…

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month