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 Inherit
s 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
Design Time View - gGlowGroupBox
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.
Private Sub gGlowBox_Layout(ByVal sender As Object, _
ByVal e As LayoutEventArgs) Handles Me.Layout
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.
Private Sub gGlowBox_Resize(ByVal sender As Object,
ByVal e As System.EventArgs) Handles Me.Resize
If IsNothing(FindForm) OrElse
FindForm.WindowState = FormWindowState.Minimized Then Exit Sub
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.
Private Sub gGlowBox_ControlAdded(ByVal sender As Object,_
ByVal e As ControlEventArgs) Handles Me.ControlAdded
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.
Private Sub ChildGotFocus()
If Controls.Count > 0 Then
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
.
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.
<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 ContainerControl
s like a Panel
and the other uses the ControlStyle
to check for controls such as UserControl
s that have this ControlStyle
set to True
.
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
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.
<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!
<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