Introduction
The DropDownPanel
is a User Control that inherits from Panel
. I wanted a combobox like dropdown control that I could drop other controls onto and arrange them at design time. I also wanted to be able to have the dropdown part to be able to be a different size from the header portion.
The Header:
- Push pin - Locks the panel open so it won't close when it loses focus.
- Dropdown button - Opens and closes the panel.
- Textbox - Displays string information.
- Graphic - Displays a small image next to the text box.
The Dropdown Panel:
- The area defined by the
PanelSize
property that holds the child controls.
Events
Public Event DropDown(ByVal sender As Object, ByVal IsOpen As Boolean)
This event will fire when the drop down button is clicked.
Public Event TextBoxChanged(ByVal sender As Object)
This event will fire when the text in the textbox changes.
ControlDesigner Methods
SizePanelToControl
Resizes the panel area to fit the current bounds of the control.
SizeToChildControls
Resizes the panel area to fit the child controls with a margin of AutoControlMargin
.
How to Use it
Drop the control on the form and size the header area to the width desired. For the panel area, open it and resize it with the PanelSize
property, or use one of the designer methods above. Drop what controls you want on it, and adjust the appearance properties to achieve the look you want. It's as easy as that.
Code #Regions
Initialize
Because the Panel
does not have a Load
event, I put the Load code in the HandleCreated
event, which gave me the same results.
Private Sub DropDownPanel_HandleCreated(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.HandleCreated
If Not Me.DesignMode Then
Select Case Me.StartUpState
Case eStartUpState.Closed
Me.CloseDropDown()
Case eStartUpState.Open
Me.OpenDropDown()
End Select
ResizeMe()
End If
End Sub
If you do not want to use the above event, delete it, and add the following code to each of the parent forms:
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
CloseDropdowns(Me.Controls)
End Sub
Sub CloseDropdowns(ByVal cc As System.Windows.Forms.Control.ControlCollection)
For Each c As Control In cc
If c.GetType Is GetType(DropDownPanel.DropDownPanel) Then
Dim ddp As DropDownPanel.DropDownPanel = c
If ddp.StartUpState = _
DropDownPanel.DropDownPanel.eStartUpState.Closed Then
ddp.CloseDropDown()
Else
ddp.OpenDropDown()
End If
End If
If c.HasChildren Then CloseDropdowns(c.Controls)
Next
End Sub
Control Properties
Here is a list of the primary properties:
StartUpState
Is the DropDownPanel
open or closed when it loads.
PanelSize
Get or set the size of the DropDownPanel
.
PanelGradientType
, PanelBackColorA
, PanelBackColorB
What color and blend type to fill the panel with.
PanelBorderColor
Get or set the border color of the DropDownPanel
.
AutoControlMargin
Get or set the right and bottom margin from the controls.
PanelCornerRadius
, TextBoxCornerRadius
Get or set the corner radius.
TextBoxGradientType
, TextBoxBackColorA
, TextBoxBackColorB
What color and blend type to fill the textbox with.
TextBoxBorderColor
Get or set the border color of the textbox.
ButtonShape
Get or set the shape of the dropdown button.
ShowPushPin
Get or set if the push pin is visible.
GraphicBorderColor
Get or set the border color around the graphic.
GraphicImage
Get or set the image next to the textbox.
GraphicWidth
Get or set the width of the graphic image.
GraphicAutoWidth
Get or set to automatically size the width from the image aspect ratio.
Mouse Events
Track if the cursor is over one of the areas like the button or the push pin.
Private Function IsMouseOverArea(ByVal X As Integer, _
ByVal Y As Integer, ByVal GP As GraphicsPath) As Boolean
Using Area As New Region(GP)
Return Area.IsVisible(X, Y)
End Using
End Function
Painting
Contains the routines to draw all the parts of the control. Any child control on the panel that is in the header area will be automatically moved down. To override this behavior, add the string "IgnoreMe
" in the child control's Tag
property, and it will be ignored.
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
If _GraphicImage IsNot Nothing Then
Dim GW As Integer = IIf(_GraphicAutoWidth, 20 * _
(_GraphicImage.Width / _GraphicImage.Height), _GraphicWidth)
e.Graphics.DrawImage(_GraphicImage, 0, 0, GW, 20)
e.Graphics.DrawRectangle(New Pen(_GraphicBorderColor), 0, 0, GW, 19)
End If
If rectTextBox.Width > _TextBoxCornerRadius * 2 Then
If Me._TextBoxGradientType = eGradientType.Solid Then
e.Graphics.FillPath(New SolidBrush(_TextBoxBackColorA), _
GetRectPath(rectTextBox, TextBoxCornerRadius))
Else
Using lgbr As LinearGradientBrush = New LinearGradientBrush _
(rectTextBox, _TextBoxBackColorA, _TextBoxBackColorB, _
CType([Enum].Parse(GetType(LinearGradientMode), _
_TextBoxGradientType.ToString), LinearGradientMode))
e.Graphics.FillPath(lgbr, GetRectPath(rectTextBox, _
TextBoxCornerRadius))
End Using
End If
e.Graphics.DrawPath(New Pen(_TextBoxBorderColor), _
GetRectPath(rectTextBox, TextBoxCornerRadius))
If _Text <> "" Then
Using sf As StringFormat = New StringFormat
sf.Alignment = StringAlignment.Center
sf.LineAlignment = StringAlignment.Center
e.Graphics.DrawString(_Text, Me.Font, _
New SolidBrush(Me.ForeColor), rectTextBox, sf)
End Using
End If
End If
If IsOpen Then
Dim rect As Rectangle = New Rectangle(0, 21, _
Me.PanelSize.Width - 1, Me.PanelSize.Height - 1)
If Me._PanelGradientType = eGradientType.Solid Then
e.Graphics.FillPath(New SolidBrush(_PanelBackColorA), _
GetRectPath(rect, PanelCornerRadius))
Else
Using lgbr As LinearGradientBrush = New LinearGradientBrush _
(rect, _PanelBackColorA, _PanelBackColorB, _
CType([Enum].Parse(GetType(LinearGradientMode), _
_PanelGradientType.ToString), LinearGradientMode))
e.Graphics.FillPath(lgbr, GetRectPath(rect, _
PanelCornerRadius))
End Using
End If
e.Graphics.DrawPath(New Pen(_PanelBorderColor), _
GetRectPath(rect, PanelCornerRadius))
End If
DrawDropDownButton(e.Graphics)
If _ShowPushPin Then e.Graphics.DrawImage(bmpPushPin, rectPushPin)
For Each c As Control In Me.Controls
If c.Location.Y < 21 Then
If c.Tag IsNot "IgnoreMe" Then _
c.Location = New Point(c.Location.X, 21)
End If
Next
End Sub
Methods
Private
UpdateTextArea
- Sets up what appears in the header.UpdateRegion
- Sets up the header and panel regions to show what is behind the control, through the cut out area.ResizeMe
- Resizes the control to fit the current state.
Public
OpenDropDown
- Opens the panel.CloseDropDown
- Closes the panel only if the push pin is up.ForceCloseDropDown
- Flips the push pin up and closes the panel.
Control Events
Contains the events for the DropDownPanel
.
DropDownPanelDesigner
The control designer that makes the SmartTag. Go Here for Designer How to's: UITypeEditorsDemo[^].
End Regions
Anchoring and Resizing
This became a headache for a while, but I eventually figured out a way. By design, resizing is handled differently if the panel is open or closed. If the panel is closed, then the HeaderWidth
adjusts with the control's width, but if it is open, then the HeaderWidth
does not change. This is handled in Sub ResizeMe
. In this, there is a variable blnIsResizeOK
which is set to False
to bypass this routine in the Resize
event until all manual sizing is done, as in the setting of the IsOpen
property, then the blnIsResizeOK
is set back to True
and ResizeMe
is manually called to properly resize. All was good until I tried to anchor the left and right side down to make the control grow and shrink with the form. If a panel was open when the form was resized, then the panel won't resize. So, I needed to make sure the DropDownPanel
is closed before resizing occurs. Unfortunately, the panel does not have a ResizeBegin
event, so you have to add this code to the parent form of any anchored DropDownPanel
.
Private Sub Form_ResizeBegin(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.ResizeBegin
CloseDDP()
End Sub
Private Sub CloseDDP()
If DropDownPanelAnchored.IsOpen Then _
DropDownPanelAnchored.ForceCloseDropDown()
If DropDownPanelDocked.IsOpen Then _
DropDownPanelDocked.ForceCloseDropDown()
End Sub
Now, the DropDownPanel
is closed before resizing begins, and all is well...until, I maximized or minimized the form. This does not trigger the form's Resize
event. To catch this, you have to override the MessageWindow.WndProc
method.
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If (m.Msg = WM_SYSCOMMAND) Then
If m.WParam.ToInt64 = SC_MAXIMIZE _
Or m.WParam.ToInt64 = SC_RESTORE _
Or m.WParam.ToInt64 = SC_MINIMIZE _
Or m.WParam.ToInt64 = SC_DblClckTitleBarMAX _
Or m.WParam.ToInt64 = SC_DblClckTitleBarRestore Then
CloseDDP()
End If
End If
MyBase.WndProc(m)
End Sub
Now again, all is well...until the form is minimized. The WndProc
was working, but somewhere during minimizing, the control was resizing itself (in a bad way). So, I needed to block the resizing if minimized.
Private Sub DropDownPanel_Resize( _
ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize
Try
If Me.FindForm.WindowState <> FormWindowState.Minimized _
And blnIsResizeOK Then
ResizeMe()
End If
Catch ex As Exception
End Try
End Sub
See Form2
for an example.
History
- Version 1.0 - October 2008.