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






4.96/5 (33投票s)
此控件使得可以在设计时在 ToolStripDropDown 中布局控件。
引言
你可能认为以前见过这个,并且你说对了一部分。我想要一个类似 ComboBox 的下拉控件,我可以将其上的其他控件拖放上去并在设计时进行排列。因为 ToolStripDropDown
在设计时无法直接处理,而且我那时还不完全理解它,所以我制作了 DropDownPanel[^],这是一个继承了 Panel 的用户控件,并具有一个炫酷的尺寸改变例程。它是一个好控件,在某些情况下效果很好,但它仍然不是我想要的真正下拉控件。然后,我有了在这里找到的想法。我决定为这个控件创建一个单独的文章,因为我觉得每一篇中的技术点足够多,不需要只是更新和替换 DropDownPanel 的文章。
ToolStripDropDown
有两个主要的障碍。首先,你在设计图面上看不到它,其次,它一次只能包含一个控件。你可以在一个 Panel
上放置一堆控件,然后将这个“一个” Panel
控件放入 ToolStripDropDown
中,但那样,你就会在设计图面上留下额外的控件,使其变得杂乱,或者你必须将它们隐藏在某个地方。然后,我有了将它隐藏在 DropDownContainer
中的想法,并在设计时像在 DropDownPanel
控件中那样显示它,而在运行时显示在 ToolStripDropDown
中,具体取决于 DesignMode
属性。
控件在 IDE 中的设计时设置
- 将
DropDownContainer
拖放到Form
上。 - 点击下拉按钮打开下拉面板。(是的,在这个版本中,按钮在设计时也能工作!稍后详述。)
- 将一个控件(例如单个
Calendar
控件,或一堆控件在一个Panel
上)拖放到下拉面板上。 - 将控件分配给
DropControl
属性。此属性使用 UITypeEditorDropControlsEditor
来缩小控件列表,使其仅包含DropDownContainer
上的控件(通常只有一个)。
控件的外观
- 修改外观属性以满足你的需求。
- 点击下拉按钮关闭下拉面板,使设计图面保持整洁。
运行时外观
DDAlignment
和DDShadow
是两个在设计时不可见的属性。
控件布局
标题栏 (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
DDBackColor
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
获取或设置要在下拉列表中显示的控件。
获取或设置下拉面板的大小(如果设置为 DropControl
,则自动调整大小)。
填充面板的颜色。(仅当 DDPadding
大于 0 时显示。)
获取或设置运行时是否显示阴影。
获取或设置下拉列表在运行时出现的位置。
获取或设置下拉列表在运行时的不透明度。
获取或设置图钉是否可见。
获取或设置图钉的配色方案。
获取或设置下拉按钮的形状。
获取或设置下拉按钮的配色方案。
用于填充文本框的颜色和混合类型。
获取或设置文本框的边框颜色。
获取或设置文本格式。
获取或设置图形周围的边框颜色。
获取或设置文本框旁边的图像。
获取或设置图形图像的宽度。
获取或设置从图像纵横比自动调整宽度。
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
方法
UpdateTextArea
- 设置标题区域显示的内容。UpdateRegion
- 设置标题和面板区域,通过镂空区域显示控件后面的内容。ResizeMe
- 调整控件大小以适应当前状态。OpenDesignDropDown
- 在设计模式下打开面板。CloseDesignDropDown
- 在设计模式下关闭面板。
.
OpenDropDown
- 打开面板。CloseDropDown
- 仅当图钉向上时关闭面板。ForceCloseDropDown
- 将图钉向上翻转并关闭面板。
Private
Public
控件事件
包含 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 月。