gGlowBox - 为聚焦的控件创建发光或阴影效果 (VB.NET)






4.89/5 (50投票s)
一个自定义面板,
引言
我非常喜欢 Chrome 网页上输入数据时,表单中聚焦的 Textbox
周围发光的效果。它确实让跟踪位置和输入数据变得更容易。我希望在 Winform 中也能实现这种效果。我曾以为,又要开始另一个庞大的控件项目了,但令我惊喜的是,我在很短的时间内,用很少的代码就让基本控件工作起来了。特别是因为我借鉴了我另一个控件的发光部分。关于发光技术是如何实现的,更多细节请查看 gLabel[^] 控件。
Using the Code
gGlowBox
继承自 Panel
,它会根据子控件(不只是 Textbox
,而是任何控件)的大小进行调整,并在子控件获得或失去焦点时激活或禁用发光效果。
只有三个新属性
获取或设置发光的颜色。
打开或关闭发光效果。
EffectType As eEffectType
GlowColor As Color
GlowOn As Boolean
使用简单:将 gGlowBox
拖到 Form
上,设置 GlowColor
,将一个控件放入 gGlowBox
中(gGlowBox
会自动调整大小以适应子控件),如果需要,可以进行调整。
在 1.0.1 版本中,应 Yogi Yang 的建议,我添加了一个可以处理一组控件的新控件。我添加了 gGlowGroupBox
,而不是替换原始控件,以提供更大的灵活性。主要的编码区别是 gGlowGroupBox
移除了重塑大小的方法。
在 1.0.2 版本中,应 N. Henrik Lauridsen 的建议,我为 gGlowGroupBox
添加了下拉阴影选项。
在 1.0.4 版本中,有人建议我实现 IExtenderProvider
,将 glow
属性扩展到添加到 gGlowGroupBox
的每个控件上。我立刻着手进行了,七年后,我终于完成了。
关注点
设计时视图 - gGlowBox
设计时视图 - gGlowGroupBox
OnPaintBackground
使用 gLabel[^] 控件中解释的技术,在 gGlowBox
的边缘绘制一个模糊的矩形。因为 gGlowBox
比子控件稍大,所以在子控件周围产生了发光效果。对于 DropShadow
,模糊矩形会以偏移量绘制。
gGlowGroupBox
作为 Panel
来容纳一组控件。当控件获得焦点时,Glow
会围绕每个控件绘制。截至此版本,可以为每个可以获得焦点的控件单独且唯一地设置发光及其颜色。
调整 gGlowBox 和子控件的大小
为了更轻松地调整大小,gGlowBox
会自动调整大小以适应子控件。
首先,如果子控件被添加或调整大小,Layout
事件会触发并调整 gGlowBox
的大小以匹配子控件。
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
其次,当 gGlowBox
被调整大小时,Resize
事件会调整子控件的大小以使其适合 gGlowBox
内部。如果 gGlowBox
被锚定,当 Parent
窗体最小化时,它不会恢复到原始大小,除非跳过此步骤,因此会检查 Form
的 WindowState
。
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
控件焦点事件连接
子控件的 GotFocus
和 LostFocus
事件在 ControlAdded
事件中添加到 gGlowBox
,并指向 ChildGotFocus
和 ChildLostFocus
方法。这些方法只是简单地打开和关闭发光。
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
另外,我不想在 Textbox
设置为 ReadOnly
时启用发光。如果子控件总是 Textbox
,这很简单,但 gGlowBox
被设计用来容纳任何基本控件。有些控件,如 Combobox
,没有这个属性。为了解决这个问题,我使用了 GetProperty
函数来检查未知控件是否具有该属性,如果有,则使用 CallByName
方法获取值。
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
与往常一样,文档有限且晦涩难懂。基本思想是定义将扩展到 gGlowGroupBox
上子控件的属性。例如,当一个 ToolTip 组件被添加到窗体时,ToolTip 文本属性会神奇地出现在窗体上的所有控件上。
要扩展的属性不像类中的标准属性那样创建,它们也不是子控件的直接一部分。gGlowGroupBox
维护一个控件引用及其属性的集合,这些属性被投影到控件上,可以在那里进行交互,并且更改不会保存到控件中,而是被投影回 IExtenderProvider
(gGlowGroupBox
) 以保存回集合中。
IExtenderProvider 和 ProvideProperty
首先,类必须实现 IExtenderProvider
。
Public Class gGlowGroupBox
Inherits Panel
Implements IExtenderProvider
其次,为每个属性添加 ProvidePropertyAttribute
到 Class
,例如 <ProvideProperty("UseEffect", GetType(Control))>
。
这是完整的 Header。虽然子控件只有三个属性,但我不得不使用四个,因为有一个序列化问题,我稍后会解释。
''' <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
第三,IExtenderProvider
必须有一个 CanExtend
函数来定义哪个子控件应该拥有扩展属性。我可以只写 If TypeOf extendee Is Control Then Return True
(其中 extendee
是 gGlowGroupBox
上的控件),但我不想让属性出现在不可聚焦的控件上,如 Label
或 Panel
。
在 CanExtend
中还有一个名为 GetControlStyle
的函数,它使用反射来获取控件的 ControlStyle
。我用它来获取 Selectable 值,因为没有可用的 focusable 属性来检查。有两个 ContainerControl
的检查。一个是查找普通的 ContainerControl
s,如 Panel
,另一个使用 ControlStyle
来检查具有此 ControlStyle
设置为 True
的控件,如 UserControl
s。
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
控件引用和属性将存储在 HashTable
中,以控件为 Key,Properties 类对象为 Value。
为了创建扩展属性,需要两个函数来 Get
和 Set
值。格式要求 Function
名称为 GetXXX 和 SetXXX,其中 XXX = ProvideProperty
属性中定义的属性名称。因此 <ProvideProperty("UseEffect", GetType(Control))>
将定义为如下所示,并使用 EnsurePropertiesExists()
函数确保返回一个有效的属性值。
<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
所有都进展顺利,直到我为 Color
创建了属性。Color
不会自动序列化。Designer 很快就让我知道了这个事实。为了解决这个问题,我创建了一个 Class
对象(Public Class SerialColor
),其中包含可以序列化为 Color
对象以及从 Color
对象反序列化的属性。在它的基本级别,Color
对象具有 A、R、G、B 属性,用于 Alpha、Red、Green 和 Blue 属性,有时还有一个 KnownColor
名称。
GetGlowColor
需要是 Color
类型,这样 PropertyGrid
中的属性在 Design 窗口中才能显示普通的 Color
类型编辑器,但它无法序列化到 Designer。为了解决这个问题,将 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
属性添加到 GetGlowColor
属性。但是值必须以某种方式序列化到 Designer,以便在构建后能够持久化,而不是重置或出错。添加了一个额外的 sGlowColor
属性,类型为 SerialColor
,并带有 Browsable(False)
属性,这样它就不会出现在 PropertyGrid
中。可序列化的颜色值被保存到 Designer 并从 Designer 读取,它获取和设置在 Design 窗口中看到的实际 GlowColor
Color 属性。搞定!
<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
历史
- 版本 1.0.0
- 版本 1.0.1
- 根据 Yogi Yang 的建议,添加了
gGlowGroupBox
- 根据 Yogi Yang 的建议,添加了
- 版本 1.0.2
- 根据 N. Henrik Lauridsen 的建议,为
gGlowGroupBox
添加了DropShadow
选项
- 根据 N. Henrik Lauridsen 的建议,为
- 版本 1.0.4
- 添加了
IExtenderProvider
,将属性扩展到gGlowGroupBox
上的每个单独控件
- 添加了