65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (50投票s)

2012 年 4 月 25 日

CPOL

6分钟阅读

viewsIcon

121499

downloadIcon

7642

一个自定义面板,在子控件获得焦点时在其周围创建辉光效果或阴影

引言

我非常喜欢 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 窗体最小化时,它不会恢复到原始大小,除非跳过此步骤,因此会检查 FormWindowState

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
控件焦点事件连接

子控件的 GotFocusLostFocus 事件在 ControlAdded 事件中添加到 gGlowBox,并指向 ChildGotFocusChildLostFocus 方法。这些方法只是简单地打开和关闭发光。

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

其次,为每个属性添加 ProvidePropertyAttributeClass,例如 <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(其中 extendeegGlowGroupBox 上的控件),但我不想让属性出现在不可聚焦的控件上,如 LabelPanel

CanExtend 中还有一个名为 GetControlStyle 的函数,它使用反射来获取控件的 ControlStyle。我用它来获取 Selectable 值,因为没有可用的 focusable 属性来检查。有两个 ContainerControl 的检查。一个是查找普通的 ContainerControls,如 Panel,另一个使用 ControlStyle 来检查具有此 ControlStyle 设置为 True 的控件,如 UserControls。

    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。

为了创建扩展属性,需要两个函数来 GetSet 值。格式要求 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
  • 版本 1.0.2
    • 根据 N. Henrik Lauridsen 的建议,为 gGlowGroupBox 添加了 DropShadow 选项
  • 版本 1.0.4
    • 添加了 IExtenderProvider,将属性扩展到 gGlowGroupBox 上的每个单独控件
© . All rights reserved.