Flat Combobox Code

D

Don

I've looked high and low for some code that will allow me to have a combobox
with a flat borderstyle. I found a few examples, but nothing that was
really usable for me. I had the following criteria that I wanted to meet,
but nothing did:

- No reliance on a separate library
- No unmanaged code
- Easy subclassing of System.Windows.Forms.Combobox (i.e. no custom control
monstrosity)
- No extra funky features that I didn't need

Some example had some very interesting features, like a multi-column
drop-down list, but, in the end, they introduced needless clutter and
complexity. So, I took one code example that I found and modified it so
that it was as generic as possible. All it does is allow a subclassed
combobox to have a flatstyle (along with the normal 3D style).

To use this code, create a new class and post the following code into it. I
tried to comment the code as much as possible, and tried to make it easy to
read. My apologies for the long lines:


--- CODE START ---


Public Class ComboBoxEx
Inherits System.Windows.Forms.ComboBox

#Region " Control Flattening Code "

#Region " Constants "

Private Const DIZ_BackColor As String = "The background color of the
control. Only applicable when BorderStyle is set to FixedSingle or None."
Private Const DIZ_ButtonColor As String = "The color of the drop-down
button on the combobox. Only applicable when BorderStyle is set to
FixedSingle or None."
Private Const DIZ_BorderColor As String = "The color of the combobox
border. Only applicable when BorderStyle is set to FixedSingle."
Private Const DIZ_BorderStyle As String = "The style of border for the
combobox control."
Private Const DIZ_ArrowColor As String = "The color of the arrow in the
drop-down button. Only applicable when BorderStyle is set to FixedSingle or
None."
Private Const DIZ_FocusRectangleColor As String = "The color of the
focus rectangle. Only applicable when BorderStyle is set to FixedSingle or
None."
Private Const DIZ_DrawFocusRectangle As String = "Should a focus
rectangle be drawn around the control? Only applicable when BorderStyle is
set to FixedSingle or None."

Private Const WM_PAINT As Integer = &HF

#End Region
#Region " Instance Variables "

Private _BorderStyle As BorderStyle = BorderStyle.Fixed3D
Private _ButtonColor As Color = SystemColors.Control
Private _BorderColor As Color = SystemColors.WindowFrame
Private _ArrowColor As Color = SystemColors.ControlText
Private _FocusRectangleColor As Color = SystemColors.Highlight
Private _DrawFocusRectangle As Boolean = False

#End Region
#Region " Instance Properties "

' NOTE: This method is only overridden to provide a new property
designer description.
'''<summary>The color of the combobox Back.</summary>
'''<remarks>Only applicable when BorderStyle is set to FixedSingle or
None.</remarks>
<System.ComponentModel.Description(DIZ_BackColor)> _
Public Overrides Property BackColor() As Color
Get
Return MyBase.BackColor
End Get
Set(ByVal Value As Color)
MyBase.BackColor = Value
End Set
End Property
'''<summary>The color of the combobox button.</summary>
'''<remarks>Only applicable when BorderStyle is set to FixedSingle or
None.</remarks>
<System.ComponentModel.Description(DIZ_ButtonColor)> _
Public Property ButtonColor() As Color
Get
Return _ButtonColor
End Get
Set(ByVal Value As Color)
_ButtonColor = Value
Me.Invalidate()
End Set
End Property
'''<summary>The color of the combobox Border.</summary>
'''<remarks>Only applicable when BorderStyle is set to
FixedSingle.</remarks>
<System.ComponentModel.Description(DIZ_BorderColor)> _
Public Property BorderColor() As Color
Get
Return _BorderColor
End Get
Set(ByVal Value As Color)
_BorderColor = Value
Me.Invalidate()
End Set
End Property
'''<summary>The combobox borderstyle.</summary>
<System.ComponentModel.Description(DIZ_BorderStyle)> _
Public Property BorderStyle() As BorderStyle
Get
Return _BorderStyle
End Get
Set(ByVal Value As BorderStyle)
_BorderStyle = Value
Me.Invalidate()
End Set
End Property
'''<summary>The color of the combobox Arrow.</summary>
'''<remarks>Only applicable when BorderStyle is set to FixedSingle or
None.</remarks>
<System.ComponentModel.Description(DIZ_ArrowColor)> _
Public Property ArrowColor() As Color
Get
Return _ArrowColor
End Get
Set(ByVal Value As Color)
_ArrowColor = Value
Me.Invalidate()
End Set
End Property
'''<summary>The color of the combobox Highlight.</summary>
'''<remarks>Only applicable when BorderStyle is set to FixedSingle or
None.</remarks>
<System.ComponentModel.Description(DIZ_FocusRectangleColor)> _
Public Property FocusRectangleColor() As Color
Get
Return _FocusRectangleColor
End Get
Set(ByVal Value As Color)
_FocusRectangleColor = Value
Me.Invalidate()
End Set
End Property
'''<summary>Should a highlight rectangle be drawn around the text area
of the control? </summary>
'''<remarks>Only applicable when BorderStyle is set to FixedSingle or
None.</remarks>
<System.ComponentModel.Description(DIZ_DrawFocusRectangle)> _
Public Property DrawFocusRectangle() As Boolean
Get
Return _DrawFocusRectangle
End Get
Set(ByVal Value As Boolean)
_DrawFocusRectangle = Value
End Set
End Property

#End Region
#Region " Event Handlers & Overridden Methods "

Protected Overrides Sub WndProc(ByRef m As Message)
MyBase.WndProc(m)

Select Case m.Msg

Case WM_PAINT

' Draw the combobox
DrawControl()

End Select

End Sub

Private Sub MaximComboBox_GotFocus(ByVal sender As Object, ByVal e As
System.EventArgs) Handles MyBase.GotFocus

' Needed for proper focus rectangle drawing
Me.Invalidate()

End Sub
Private Sub MaximComboBox_LostFocus(ByVal sender As Object, ByVal e As
System.EventArgs) Handles MyBase.LostFocus

' Needed for proper focus rectangle drawing
Me.Invalidate()

End Sub

#End Region
#Region " Instance Methods "

'''<summary>Draw the combobox control if the style is not 3D.</summary>
Private Sub DrawControl()


' If we are not supposed to give the control 3D borders...
' NOTE: Since all the drawing that we're doing here is done overtop
of the original control, not drawing anything will have
' the net result of the combobox appearing normally (i.e. with
3D borders).
If _BorderStyle <> BorderStyle.Fixed3D Then

Const BUTTON_WIDTH As Integer = 18 ' Width of the
drop-down button. (Is this always 18?)
Const DISABLED_ARROW_DARKEN_AMOUNT As Byte = 32 ' how much
should the drop-down arrow be darkened when the control is disabled?

Dim g As Graphics = Me.CreateGraphics ' Graphics object to
draw on
Dim backcolorBrush As Brush ' Backcolor of the
control
Dim buttonX As Integer ' Drop-down button left
coordinate
Dim buttonY As Integer ' Drop-down button top
coordinate
Dim buttonWidth As Integer ' Drop-down button width
Dim buttonHeight As Integer ' Drop-down button
height
Dim buttonRect As Rectangle ' Drop-down button area
rectangle
Dim dropButtonBrush As Brush ' Brush to draw
drop-down button with
Dim borderPen As Pen ' Pen to draw borders
with
Dim focusRectPen As Pen ' Pen to draw focus
rectangle with
Dim arrowPath As Drawing2D.GraphicsPath ' Path used to draw the
drop-down button arrow
Dim arrowTopLeft As PointF ' Top-left point for the
drop-down button arrow
Dim arrowTopRight As PointF ' Top-right point for
the drop-down button arrow
Dim arrowBottom As PointF ' Bottom point for the
drop-down button arrow
Dim arrowBrush As Brush ' Brush to draw the
drop-down button arrow with


' Get a Graphics object for the control
g = Me.CreateGraphics


' Get the background color (make it look greyed out if the
control is disabled)
If Me.Enabled Then
backcolorBrush = New SolidBrush(Me.BackColor)
Else
backcolorBrush = New SolidBrush(SystemColors.Control)
End If

' Fill in the background of the control
g.FillRectangle(backcolorBrush, Me.ClientRectangle)


' Draw the background of the dropdown button
buttonX = Me.Width - BUTTON_WIDTH
buttonY = 0
buttonWidth = BUTTON_WIDTH
buttonHeight = Me.Height
buttonRect = New Rectangle(buttonX, buttonY, buttonWidth,
buttonHeight)
dropButtonBrush = New SolidBrush(_ButtonColor)
g.FillRectangle(dropButtonBrush, buttonRect)


' If we are to draw a border...
If _BorderStyle = BorderStyle.FixedSingle Then

' Draw a border around the control and between the text area
and the drop-down button
borderPen = New Pen(_BorderColor)
With Me.ClientRectangle
g.DrawRectangle(borderPen, .X, .Y, .Width - 1, .Height -
1) ' Border around control
g.DrawLine(borderPen, buttonX, buttonY, buttonX,
buttonHeight) ' Line between text area and drop-down button
End With

End If


' If we are to draw a focus rectangle...
If _DrawFocusRectangle And Me.Focused Then

' Draw a border around the text area of the control
focusRectPen = New Pen(_FocusRectangleColor)

With Me.ClientRectangle
If _BorderStyle = BorderStyle.FixedSingle Then
' Note: We don't want to draw over the actual border
of the control, so we will draw it inside by one pixel
g.DrawRectangle(focusRectPen, .X + 1, .Y + 1,
..Width - 3, .Height - 3)
ElseIf _BorderStyle = BorderStyle.None Then
' Note: Just like drawing a border!
g.DrawRectangle(focusRectPen, .X, .Y, .Width - 1,
..Height - 1)
End If
End With

End If


' Create the path for the arrow. This is basically an outline
of the arrow's shape which we will "fill in"
arrowPath = New Drawing2D.GraphicsPath
arrowTopLeft = New PointF(Me.Width - 13, CSng((Me.Height - 5) /
2)) ' TODO: This code should be cleaned
arrowTopRight = New PointF(Me.Width - 6, CSng((Me.Height - 5) /
2)) ' up so that it relies less on
arrowBottom = New PointF(Me.Width - 9, CSng((Me.Height + 2) /
2)) ' literals.
arrowPath.AddLine(arrowTopLeft, arrowTopRight)
arrowPath.AddLine(arrowTopRight, arrowBottom)

' Draw the arrow nice and smooth-like, see? Yeah...
g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality

' Get the arrow color (you might want to make it look greyed out
if the control is disabled, though)
arrowBrush = New SolidBrush(_ArrowColor)

' Draw the arrow
g.FillPath(arrowBrush, arrowPath)


End If


End Sub

#End Region

#End Region

End Class


--- CODE END ---


I have not extensively tested this, so if anyone finds any bugs or notices
anything that could be improved, please feel free to post your thoughts. I
just wanted to post this before I forgot so that others might benefit from
it.

- Don
 
D

Don

I've discovered that the selected text does not display when this combobox's
DropDownStyle is set to DropDownList. To fix this, you need to add the
following code:

' We only need to do this for DropDownLists
If Me.DropDownStyle = ComboBoxStyle.DropDownList Then

Dim x As Integer = 2
Dim y As Integer = 4
Dim textbrush As SolidBrush

' Create a brush to draw with
textbrush = New SolidBrush(Me.ForeColor)

' Draw the text
g.DrawString(Me.Text, Me.Font, textbrush, x, y)

End If


This should be placed after the following line of code in DrawControl:


' Draw the arrow
g.FillPath(arrowBrush, arrowPath)


Also, I've discovered that the combobox is really messed up when you set
DropDownStyle to Simple. It'll take some extra work to fix that (which I
don't think I'll bother with).

- Don
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top