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

DropDownContainer - 使用 ToolStripDropDown (VB.NET) 的自定义下拉容器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (33投票s)

2009年1月1日

CPOL

7分钟阅读

viewsIcon

120563

downloadIcon

4658

此控件使得可以在设计时在 ToolStripDropDown 中布局控件。

引言

你可能认为以前见过这个,并且你说对了一部分。我想要一个类似 ComboBox 的下拉控件,我可以将其上的其他控件拖放上去并在设计时进行排列。因为 ToolStripDropDown 在设计时无法直接处理,而且我那时还不完全理解它,所以我制作了 DropDownPanel[^],这是一个继承了 Panel 的用户控件,并具有一个炫酷的尺寸改变例程。它是一个好控件,在某些情况下效果很好,但它仍然不是我想要的真正下拉控件。然后,我有了在这里找到的想法。我决定为这个控件创建一个单独的文章,因为我觉得每一篇中的技术点足够多,不需要只是更新和替换 DropDownPanel 的文章。

ToolStripDropDown 有两个主要的障碍。首先,你在设计图面上看不到它,其次,它一次只能包含一个控件。你可以在一个 Panel 上放置一堆控件,然后将这个“一个” Panel 控件放入 ToolStripDropDown 中,但那样,你就会在设计图面上留下额外的控件,使其变得杂乱,或者你必须将它们隐藏在某个地方。然后,我有了将它隐藏在 DropDownContainer 中的想法,并在设计时像在 DropDownPanel 控件中那样显示它,而在运行时显示在 ToolStripDropDown 中,具体取决于 DesignMode 属性。

控件在 IDE 中的设计时设置

  1. DropDownContainer 拖放到 Form 上。
  2. 点击下拉按钮打开下拉面板。(是的,在这个版本中,按钮在设计时也能工作!稍后详述。)
  3. 将一个控件(例如单个 Calendar 控件,或一堆控件在一个 Panel 上)拖放到下拉面板上。
  4. 将控件分配给 DropControl 属性。此属性使用 UITypeEditor DropControlsEditor 来缩小控件列表,使其仅包含 DropDownContainer 上的控件(通常只有一个)。

控件的外观

  1. 修改外观属性以满足你的需求。
  2. 点击下拉按钮关闭下拉面板,使设计图面保持整洁。

运行时外观

  1. DDAlignmentDDShadow 是两个在设计时不可见的属性。

控件布局

标题栏 (Header)

  • 图钉 - 锁定 Panel 以便在失去焦点时不会关闭(现已动画化!)。
  • 下拉按钮 - 打开和关闭 Panel
  • 文本框 - 显示字符串信息。
  • 图形 - 在文本框旁边显示一个小图像。

设计时的下拉 Panel

  • PanelSize 属性定义的容纳 DropControl 的区域。

事件

  • Public Event DropDown(ByVal sender As Object, ByVal IsOpen As Boolean)
  • 当点击下拉按钮时,此事件将触发。

  • Public Event TextBoxChanged(ByVal sender As Object)
  • 当文本框中的文本更改时,此事件将触发。

代码 #Regions

初始化

由于没有 Load 事件,我将加载代码放在了 HandleCreated 事件中,结果相同。

要首先设置一个 ToolStripDropDown,请将 DropControl 放入 ToolStripControlHost 中。

TSHost = New ToolStripControlHost(_DropControl)

然后,将 ToolStripControlHost 添加到 ToolStripDropDown 中,并从 DropDownContainer 控件中移除 DropControl

TSDropDown.Items.Add(TSHost)
Me.Controls.Remove(_DropControl)

这是完整的实现方法

Private Sub DDContainer_HandleCreated(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Me.HandleCreated

    'I put this here because there is no Load Event
    If Not Me.DesignMode Then

        Me.CloseDesignDropDown()
        Me.Region = Nothing

        blnIsResizeOK = True

        If _DropControl IsNot Nothing Then

            TSHost = New ToolStripControlHost(_DropControl)
            TSHost.Margin = Padding.Empty
            TSHost.Padding = Padding.Empty
            TSHost.AutoSize = False
            TSHost.Size = _DropControl.Size

            TSDropDown.Size = TSHost.Size
            TSDropDown.Items.Add(TSHost)
            TSDropDown.BackColor = _DDBackColor
            TSDropDown.DropShadowEnabled = _DDShadow
            Me.Controls.Remove(_DropControl)
        End If
    End If

    ResizeMe()
End Sub

控件属性

以下是主要属性列表

  • DropControl
  • 获取或设置要在下拉列表中显示的控件。

  • PanelSize
  • 获取或设置下拉面板的大小(如果设置为 DropControl,则自动调整大小)。

  • DDBackColor
  • 填充面板的颜色。(仅当 DDPadding 大于 0 时显示。)

  • DDShadow
  • 获取或设置运行时是否显示阴影。

  • DDAlignment
  • 获取或设置下拉列表在运行时出现的位置。

  • DDOpacity
  • 获取或设置下拉列表在运行时的不透明度。

  • ShowPushPin
  • 获取或设置图钉是否可见。

  • Pin, PinBody, PinHighlight, PinOutline
  • 获取或设置图钉的配色方案。

  • ButtonShape
  • 获取或设置下拉按钮的形状。

  • ButtonBackColor, ButtonHighlight, ButtonForeColor, ButtonBorder
  • 获取或设置下拉按钮的配色方案。

  • TextBoxGradientType, TextBoxBackColorA, TextBoxBackColorB
  • 用于填充文本框的颜色和混合类型。

  • TextBoxBorderColor
  • 获取或设置文本框的边框颜色。

  • Text, ForeColor, TextShadow, TextShadowColor
  • 获取或设置文本格式。

  • GraphicBorderColor
  • 获取或设置图形周围的边框颜色。

  • GraphicImage
  • 获取或设置文本框旁边的图像。

  • GraphicWidth
  • 获取或设置图形图像的宽度。

  • GraphicAutoWidth
  • 获取或设置从图像纵横比自动调整宽度。

ToolStripDropDown

包含 ToolStripDropDown 的打开和关闭事件。关闭下拉菜单很简单,当它失去焦点时会自动关闭。但是,如果你想通过再次点击按钮来关闭它,你会遇到一个问题,因为当你点击按钮时,你是在下拉区域之外,这会在按钮实际点击之前自动关闭它,所以它只是重新打开它。为了解决这个问题,我必须在下拉菜单尝试关闭时检查鼠标是否在按钮区域上方,并取消关闭。

Private Sub TSDropDown_Opening(ByVal sender As Object, _
        ByVal e As CancelEventArgs)
    RaiseEvent DropDown(Me, True)
End Sub
Private Sub TSDropDown_Closing(ByVal sender As Object, _
        ByVal e As ToolStripDropDownClosingEventArgs)
    Try
        If (Not GetButtonPath.IsVisible(PointToClient(Control.MousePosition)) _
            Or (e.CloseReason = ToolStripDropDownCloseReason.Keyboard)) _
            And Not CBool(_PushPinState) Then
            IsOpen = False
        End If

        e.Cancel = CBool(_PushPinState)

        If Not e.Cancel Then
            Me.Invalidate()
            RaiseEvent DropDown(Me, False)

        End If
    Catch ex As Exception

    End Try
End Sub

鼠标事件

跟踪光标是否在按钮或图钉等区域上方。如果控件处于设计模式,则调整大小以显示面板,如果在运行时,则显示 ToolStripDropDown。但等等,通常情况下,当你在设计时点击控件时,它只是选择控件。通过将 ControlDesigner.GetHitTest() 方法添加到 DDContainerDesigner,可以检查鼠标位置是否在按钮上方,并将点击传递过去,从而允许它触发设计器中的鼠标事件。现在,在设计时点击下拉按钮可以打开和关闭面板。

Protected Overrides Function GetHitTest _
         (ByVal point As System.Drawing.Point) As Boolean

    Dim DDC As DDContainer = CType(Component, DDContainer)
    point = DDC.PointToClient(point)
    Return DDC.GetButtonPath.IsVisible(point)

End Function

你可能还会注意到,在设计时,当控件关闭时,它只能调整宽度(就像单个的 TextBox 一样),但当它打开时,你可以调整所有尺寸。这也通过在 DDContainerDesigner 中重写 ControlDesigner.SelectionRules 属性来实现。

Public Overrides ReadOnly Property SelectionRules() _
       As System.Windows.Forms.Design.SelectionRules
    Get
        Dim DDC As DDContainer = CType(Component, DDContainer)
        If DDC.IsOpen Then
            Return MyBase.SelectionRules
        Else
            Return SelectionRules.LeftSizeable _
                   Or SelectionRules.RightSizeable _
                   Or Windows.Forms.Design.SelectionRules.Visible _
                   Or Windows.Forms.Design.SelectionRules.Moveable
        End If
    End Get
End Property

为了刷新选择矩形,我在 MouseUp 事件中重置了 ISelectionService

Dim selectservice As ISelectionService = _
    CType(GetService(GetType(ISelectionService)), ISelectionService)
Dim selection As New ArrayList
selection.Clear()
selectservice.SetSelectedComponents(selection, SelectionTypes.Replace)
selection.Add(Me)
selectservice.SetSelectedComponents(selection, SelectionTypes.Add)

既然我们在谈论 DDContainerDesigner,那么我们就来处理 OnPaintAdornments 方法。这是我们可以添加一个额外的绘制例程的地方,它发生在控件绘制自身之后,并且只在设计时发生。在这里,在设计时绘制下拉面板的表示,以便你看到在哪里可以拖放 DropControl

Protected Overrides Sub OnPaintAdornments _
         (ByVal pe As System.Windows.Forms.PaintEventArgs)
    MyBase.OnPaintAdornments(pe)
    Dim DDC As DDContainer = CType(Component, DDContainer)
    If DDC.IsOpen Then
        Dim rect As Rectangle = New Rectangle(0, DDC.HeaderHeight + 2, _
            DDC.PanelSize.Width - 1, DDC.PanelSize.Height - 1)
        Using g As Graphics = pe.Graphics
            g.FillRectangle(New SolidBrush(DDC.DDBackColor), rect)
            Using pn As Pen = New Pen(Color.Gray, 1)
                pn.DashStyle = DashStyle.Dash
                g.DrawRectangle(pn, rect)
            End Using
        End Using
    End If
End Sub

绘制

包含绘制控件每个部分的例程。添加到 DropDownContainer 中并且位于标题区域的任何控件都会被自动向下移动。要覆盖此行为,请在子控件的 Tag 属性中添加字符串 "IgnoreMe",它将被忽略。

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
    'Draw the Graphic if available and resize
    If _GraphicImage IsNot Nothing Then
        Dim GW As Integer = CInt(IIf(_GraphicAutoWidth, _
             _HeaderHeight * (_GraphicImage.Width / _GraphicImage.Height) _
             , _GraphicWidth))
        e.Graphics.DrawImage(_GraphicImage, 0, 0, GW, _HeaderHeight)
        e.Graphics.DrawRectangle(New Pen(_GraphicBorderColor), _
                                 0, 0, GW, _HeaderHeight - 1)
    End If

    'Draw the Text Box
    If rectTextBox.Width > _TextBoxCornerRadius * 2 Then DrawTextBox(e.Graphics)

    'Draw the Drop Down Button
    DrawDropDownButton(e.Graphics)

    'Draw the Push Pin
    If _ShowPushPin Then DrawPushPin(e.Graphics)

    'Adjust any miss placed control positioned on the Header
    For Each c As Control In Me.Controls
        If c.Location.Y < _HeaderHeight + 1 Then
            If CStr(c.Tag) <> "IgnoreMe" Then
                c.Location = New Point(c.Location.X, _HeaderHeight + 1)
            End If
        End If
    Next
End Sub

DropDownContainer 中,图钉现在会旋转而不是简单地翻转,并且可以更改颜色。图钉不是使用固定的位图,而是使用 GraphicsPath 在旋转的 Graphics 曲面上手动绘制任何颜色。通过使用由 Timer 增量的旋转 Matrix 来转换 Graphics 对象来实现旋转。

Sub DrawPushPin(ByRef g As Graphics)
    g.SmoothingMode = SmoothingMode.AntiAlias
    Dim gp As New GraphicsPath
    Dim mx As New Matrix
    mx.RotateAt(PushPinRotate, New Point(rectPushPin.X + 10, 9))
    g.Transform = mx

    gp.FillMode = FillMode.Winding
    gp.AddEllipse(rectPushPin.X + 6, 0, 8, 4)
    gp.AddEllipse(rectPushPin.X + 3, 7, 14, 6)
    gp.AddRectangle(New Rectangle(rectPushPin.X + 7, 3, 6, 8))
    g.FillPath(New LinearGradientBrush _
        (New Rectangle(rectPushPin.X + 2, 0, 14, 18), _
        _PinHighlight, _PinBody, _
         LinearGradientMode.Horizontal), gp)

    Using pn As Pen = New Pen(_Pin, 3)
        pn.EndCap = LineCap.Triangle
        g.DrawLine(pn, rectPushPin.X + 10, 13, rectPushPin.X + 10, 18)
    End Using

    gp.Reset()
    gp.AddEllipse(rectPushPin.X + 6, 0, 8, 4)
    gp.AddArc(rectPushPin.X + 3, 7, 14, 6, 326, 246)
    gp.StartFigure()
    gp.AddLine(rectPushPin.X + 7, 3, rectPushPin.X + 7, 9)
    gp.StartFigure()
    gp.AddLine(rectPushPin.X + 13, 3, rectPushPin.X + 13, 9)
    gp.StartFigure()
    gp.AddArc(rectPushPin.X + 7, 7, 6, 3, 0, 150)
    g.DrawPath(New Pen(_PinOutline, 1), gp)

    gp.Dispose()
    mx.Dispose()
End Sub

方法

    Private

  • UpdateTextArea - 设置标题区域显示的内容。
  • UpdateRegion - 设置标题和面板区域,通过镂空区域显示控件后面的内容。
  • ResizeMe - 调整控件大小以适应当前状态。
  • OpenDesignDropDown - 在设计模式下打开面板。
  • CloseDesignDropDown - 在设计模式下关闭面板。
  • .

    Public

  • OpenDropDown - 打开面板。
  • CloseDropDown - 仅当图钉向上时关闭面板。
  • ForceCloseDropDown - 将图钉向上翻转并关闭面板。

控件事件

包含 DropDownContainer 的事件。

DropDownContainer 设计器和编辑器

创建 SmartTag 的 ControlDesigner。在此处查找本文尚未讨论的设计器操作方法:UITypeEditorsDemo[^]。

End Regions

锚定和调整大小

锚定在这个控件中不像在 DropDownPanel 中那样构成大问题,只需确保在设计模式下调整窗体大小时它已关闭。对于窗体大小调整,移动或切换到不同的窗口无法被 DropDownContainer 检测到,这只有在图钉向下时才是一个问题。为了消除这个问题,请在父窗体中添加两个事件,如果你正在使用图钉,则强制 DropDownContainer 关闭。

添加每个 DropDownContainer 来关闭它们。

Private Sub Form1_ResizeBegin(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Me.ResizeBegin
    DropDownContainer1.ForceCloseDropDown()
    DropDownContainer2.ForceCloseDropDown()
    ...
End Sub

Private Sub Form_Move(ByVal sender As Object, _
  ByVal e As System.EventArgs) Handles Me.Move
    DropDownContainer1.ForceCloseDropDown()
    DropDownContainer2.ForceCloseDropDown()
    ...
End Sub

或者,如果你不确定,请使用以下例程。

Private Sub Form1_Move(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Me.Move
    For Each c As Control In Me.Controls
        If c.GetType Is GetType(DropDownContainer.DDContainer) Then
            CType(c, DropDownContainer.DDContainer).ForceCloseDropDown()
        End If
    Next
End Sub

Private Sub Form1_ResizeBegin(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Me.ResizeBegin
    For Each c As Control In Me.Controls
        If c.GetType Is GetType(DropDownContainer.DDContainer) Then
            CType(c, DropDownContainer.DDContainer).ForceCloseDropDown()
        End If
    Next
End Sub

历史

  • 版本 1.0 - 2009 年 1 月。
© . All rights reserved.