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

gGlowBox - Create a Glow or Shadow Effect Around a Focused Control (VB.NET)

4.89/5 (50 votes)
28 Feb 2019CPOL6 min read 119.4K   7.6K  
A custom Panel that creates a glow effect around a child control or a drop shadow when it receives focus

Image 1

Introduction

I really like the glow effect around a focused Textbox when inputting data on a form on a Chrome web page. It really makes it easier to track your position and enter data. I wanted to have this same effect on a Winform. I thought, here we go again with another big control project, but to my surprise I got the basic control working in no time with very little code. Especially since I borrowed the glow part from one of my other controls. For more details on how the glow technique is achieved, check out the gLabel[^] control.

Using the Code

The gGlowBox Inherits a Panel, resizes to fit one child control (any control, not just a Textbox), and activates and deactivates the glow effect when the child control gets or loses focus.

There are only three new properties:

Get or set the color of the glow.

Turn the glow effect on or off.

  • EffectType As eEffectType
  • GlowColor As Color
  • GlowOn As Boolean

Simple to use: Drop the gGlowBox on a Form, set the GlowColor, drop a control in the gGlowBox (the gGlowBox will resize automatically to fit the child control), and resize if needed.

In version 1.0.1, from Yogi Yang's suggestion, I added a new control that can handle a group of controls. I added the gGlowGroupBox rather than replace the original control to allow more flexibility. The main coding difference is the gGlowGroupBox has the re-sizing methods removed.

In version 1.0.2, from N. Henrik Lauridsen's suggestion, I added a Drop Shadow option to the gGlowGroupBox.

In version 1.0.4, it was suggested that I implement the IExtenderProvider to extend the glow property to each control added to the gGlowGroupBox. Well, I jumped right on it and a short seven years later, I did it.

Points of Interest

Design Time View - gGlowBox

Image 2

Design Time View - gGlowGroupBox

Image 3

OnPaintBackground

Using the technique explained in the gLabel[^] control, a blurred rectangle is drawn around the edge of the gGlowBox. Because the gGlowBox is slightly bigger than the child control, it creates a glowing effect around the child control. For the DropShadow, the Blurred Rectangle is drawn at an offset.

The gGlowGroupBox acts as a Panel to hold a group of controls. The Glow will be painted around each control when it recieves focus. As of this version, the glow and its color can be set individually and uniquely for each control that can receive focus.

Sizing the gGlowBox and the Child Control

To make sizing easier, the gGlowBox sizes itself automatically to the child control.

First, if the child control is Added or Resized, the Layout event fires and resizes the gGlowBox to match up with the child control.

VB.NET
Private Sub gGlowBox_Layout(ByVal sender As Object, _
        ByVal e As LayoutEventArgs) Handles Me.Layout

    'Resize the gGlowBox to fit in the Child Control size
    If Controls.Count > 0 Then
        If e.AffectedControl Is Controls(0) Then
            Size = New Size(Controls(0).Width + 7, Controls(0).Height + 7)
            Controls(0).Location = New Point(4, 4)
        End If

    End If

End Sub

Second, when the gGlowBox is resized, the Resize event will size the child control to fit inside the gGlowBox. An anchored gGlowBox will not return to its original size when the Parent form is minimised unless this step is skipped, so a check is made to see what the Form's WindowState is first.

VB
Private Sub gGlowBox_Resize(ByVal sender As Object, 
       ByVal e As System.EventArgs) Handles Me.Resize

    'This is needed to avoid resizing an Anchored gGlowBox 
    ' when the parent Form is Minimized 
    If IsNothing(FindForm) OrElse 
       FindForm.WindowState = FormWindowState.Minimized Then Exit Sub

    'Resize the Child Control to fit the size of the gGlowBox
    If Controls.Count > 0 Then
        Controls(0).Size = New Size(Width - 7, Height - 7)
    End If

End Sub
Control Focus Event Wire-up

The child control's GotFocus and LostFocus events are added to the gGlowBox in the ControlAdded event and point to the ChildGotFocus and ChildLostFocus methods. These methods simply turn the glow on and off.

VB
Private Sub gGlowBox_ControlAdded(ByVal sender As Object,_
       ByVal e As ControlEventArgs) Handles Me.ControlAdded
   ' Add handlers to let the gGlowBox know
   ' when the child control gets Focus
   AddHandler e.Control.GotFocus, AddressOf ChildGotFocus
   AddHandler e.Control.LostFocus, AddressOf ChildLostFocus
End Sub

As an extra note, I did not want the glow to turn on if the Textbox was set to ReadOnly. Simple if the child control was always going to be a Textbox, but the gGlowBox was designed to house any basic control. Some controls like the Combobox do not have this property. To deal with this, I used the GetProperty function to check if the unknown control has that property and if it does, use the CallByName method to get the value.

VB
Private Sub ChildGotFocus()

    If Controls.Count > 0 Then
        'Check if the control has the ReadOnly 
        'property and if so, its value.
        If Not IsNothing(Controls(0).GetType().GetProperty("ReadOnly")) Then
            GlowOn = Not CallByName(Controls(0), "ReadOnly", CallType.Get)
        Else
            GlowOn = True
        End If

    End If
End Sub

Private Sub ChildLostFocus()
    GlowOn = False
End Sub

IExtenderProvider

As usual, documentation was limited and obscure. The basic idea is to define the properties that will extend to the child controls on the gGlowGroupBox. An example is like when a ToolTip component is added to the form and the ToolTip text property appears magically on all the controls on the form.
The properties to be extended are not created like a standard property in a class, and they are not actually a direct part of the child control. The gGlowGroupBox keeps a collection of the references to each control and its properties which are projected to the control where it can be interacted with and the change is not saved to the control, but instead is projected back to the IExtenderProvider (gGlowGroupBox) to be saved back into the Collection.

IExtenderProvider and ProvideProperty

First of all, the Class must Implement the IExtenderProvider.

VB
Public Class gGlowGroupBox
Inherits Panel
Implements IExtenderProvider

Secondly, the ProvidePropertyAttribute is added to the Class for each property like this <ProvideProperty("UseEffect", GetType(Control))>.

This is the complete Header. Though the child controls only have three properties, I had to use four due to a serialization issue that I will explain shortly.

VB
''' <summary>
''' Panel Control to add Glow Effect to all of the Child Controls
''' </summary>
''' <remarks>v1.0.4</remarks>
<DebuggerStepThrough()>
<ProvideProperty("UseEffect", GetType(Control))>
<ProvideProperty("GlowColor", GetType(Control))>
<ProvideProperty("sGlowColor", GetType(Control))>
<ProvideProperty("EffectType", GetType(Control))>
<ToolboxItem(True), ToolboxBitmap(GetType(gGlowBox), "gControlLib.gGlowGroupBox.bmp")>
Public Class gGlowGroupBox
    Inherits Panel
    Implements IExtenderProvider
.
.
.
End Class

Third, the IExtenderProvider must have a CanExtend Function to define which child control should have the extended properties. I could have just put If TypeOf extendee Is Control Then Return True (where extendee is the control on the gGlowGroupBox), but I do not want the properties to appear on an non-focusable controls like a Label or Panel.

Also in the CanExtend is a function called GetControlStyle that uses Reflection to get the ControlStyle out of the control. I used this to get the Selectable value since there isn't a focusable property to check. There are two checks for ContainerControl. One is to find normal ContainerControls like a Panel and the other uses the ControlStyle to check for controls such as UserControls that have this ControlStyle set to True.

VB
Public Function CanExtend(extendee As Object) As Boolean Implements IExtenderProvider.CanExtend

    If (TypeOf extendee Is Control AndAlso CType(extendee, Control).Parent Is Me) Then

        If GetControlStyle(CType(extendee, Control), ControlStyles.ContainerControl) = False AndAlso
           GetControlStyle(CType(extendee, Control), ControlStyles.Selectable) = True Then
            Return True
        End If

        If TypeOf extendee Is ContainerControl Then Return False

    End If

    Return False

End Function
Properties Class
VB
Private Class GlowProperties
    Public UseEffect As Boolean
    Public GlowColor As Color
    Public EffectType As eEffectType

    Public Sub New(GlowColorDef As Color, EffectTypeDef As eEffectType)
        UseEffect = True
        GlowColor = GlowColorDef
        EffectType = EffectTypeDef
    End Sub
End Class

Private glowProps As New Hashtable
The control references and properties will be stored in a HashTable with the control as the Key and the Properties class object as the Value.

To create the extended properties, two functions are needed to Get and Set the values. The format requires that the Function name is GetXXX and SetXXX where XXX = Property Name defined in the ProvideProperty Attribute. So <ProvideProperty("UseEffect", GetType(Control))> would be defined like this with the EnsurePropertiesExists() function ensuring there is a valid property value returned.

VB
<DefaultValue(True)>
<Category("GlowBox")>
<DisplayName("Use Effect")>
<Description("Set if this Control creates a Focus Glow")>
Public Function GetUseEffect(g As Control) As Boolean
    Return EnsurePropertiesExists(g).UseEffect
End Function

Public Sub SetUseEffect(g As Control, value As Boolean)
    EnsurePropertiesExists(g).UseEffect = value
    g.Invalidate()
End Sub

All went well until I created the Property for the Color. Color does not Serialize automatically. The Designer quickly let me know this fact. To get around this problem, I created a Class object (Public Class SerialColor) with properties that could be serialized to and from a Color object. At its base level, the Color object has A, R, G, B Properties for the Alpha, Red, Green, and Blue properties, and sometimes a KnownColor Name.

The GetGlowColor needs to be of Type Color so the property in the PropertyGrid has the normal Color Type Editor in the Design window, but it can't be serialized to the Designer. To fix this, the Attribute DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) is added to the GetGlowColor Property. But the value must be serialized somehow to the designer so it will persist after a build and not reset or error out. An extra property (sGlowColor) of Type SerialColor is added with the Browsable(False) Attribute so it will not appear in the PropertyGrid. The serializable color value is saved to and from the designer and gets and sets the real GlowColor Color property seen in the Design window. TaDa!

VB
<DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
Public Function GetGlowColor(g As Control) As Color
    Return EnsurePropertiesExists(g).GlowColor
End Function

Public Sub SetGlowColor(g As Control, value As Color)
    EnsurePropertiesExists(g).GlowColor = value
    g.Invalidate()
End Sub

<Browsable(False)>
Public Function GetsGlowColor(g As Control) As SerialColor
    Return New SerialColor(EnsurePropertiesExists(g).GlowColor)
End Function

Public Sub SetsGlowColor(g As Control, value As SerialColor)
    EnsurePropertiesExists(g).GlowColor = value.DeserializeColor()
    g.Invalidate()
End Sub

History

  • Version 1.0.0
  • Version 1.0.1
    • Added gGlowGroupBox from Yogi Yang's suggestion
  • Version 1.0.2
    • Added DropShadow Option to the gGlowGroupBox from N. Henrik Lauridsen's suggestion
  • Version 1.0.4
    • Added IExtenderProvider to extend the properties to each individual control on the gGlowGroupBox

License

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