How to make a custom ComboBox (OwnerDrawFixed) looks 3D like the standard ComboBox?

pepoluan picture pepoluan · May 3, 2011 · Viewed 17.2k times · Source

I am making a custom ComboBox, inherited from Winforms' standard ComboBox. For my custom ComboBox, I set DrawMode to OwnerDrawFixed and DropDownStyle to DropDownList. Then I write my own OnDrawItem method. But I ended up like this:

Standard vs Custom ComboBoxes

How do I make my Custom ComboBox to look like the Standard one?


Update 1: ButtonRenderer

After searching all around, I found the ButtonRenderer class. It provides a DrawButton static/shared method which -- as the name implies -- draws the proper 3D button. I'm experimenting with it now.


Update 2: What overwrites my control?

I tried using the Graphics properties of various objects I can think of, but I always fail. Finally, I tried the Graphics of the form, and apparently something is overwriting my button.

Here's the code:

Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
  Dim TextToDraw As String = _DefaultText
  __Brush_Window.Color = Color.FromKnownColor(KnownColor.Window)
  __Brush_Disabled.Color = Color.FromKnownColor(KnownColor.GrayText)
  __Brush_Enabled.Color = Color.FromKnownColor(KnownColor.WindowText)
  If e.Index >= 0 Then
    TextToDraw = _DataSource.ItemText(e.Index)
  End If
  If TextToDraw.StartsWith("---") Then TextToDraw = StrDup(3, ChrW(&H2500)) ' U+2500 is "Box Drawing Light Horizontal"
  If (e.State And DrawItemState.ComboBoxEdit) > 0 Then
    'ButtonRenderer.DrawButton(e.Graphics, e.Bounds, VisualStyles.PushButtonState.Default)
  Else
    e.DrawBackground()
  End If
  With e
    If _IsEnabled(.Index) Then
      .Graphics.DrawString(TextToDraw, Me.Font, __Brush_Enabled, .Bounds.X, .Bounds.Y)
    Else
      '.Graphics.FillRectangle(__Brush_Window, .Bounds)
      .Graphics.DrawString(TextToDraw, Me.Font, __Brush_Disabled, .Bounds.X, .Bounds.Y)
    End If
  End With
  TextToDraw = Nothing
  ButtonRenderer.DrawButton(Me.Parent.CreateGraphics, Me.ClientRectangle, VisualStyles.PushButtonState.Default)

  'MyBase.OnDrawItem(e)
End Sub

And here's the result:

Overwritten ButtonRenderer

Replacing Me.Parent.CreateGraphics with e.Graphics got me this:

Clipped ButtonRenderer

And doing the above + replacing Me.ClientRectangle with e.Bounds got me this:

Shrunk ButtonRenderer

Can anyone point me whose Graphics I must use for the ButtonRenderer.DrawButton method?

PS: The bluish border is due to my using PushButtonState.Default instead of PushButtonState.Normal


I Found An Answer! (see below)

Answer

pepoluan picture pepoluan · May 10, 2011

I forgot where I found the answer... I'll edit this answer when I remember.

But apparently, I need to set the Systems.Windows.Forms.ControlStyles flags. Especially the ControlStyles.UserPaint flag.

So, my New() now looks like this:

Private _ButtonArea as New Rectangle

Public Sub New()
  ' This call is required by the designer.
  InitializeComponent()
  ' Add any initialization after the InitializeComponent() call.
  MyBase.SetStyle(ControlStyles.Opaque Or ControlStyles.UserPaint, True)
  MyBase.DrawMode = Windows.Forms.DrawMode.OwnerDrawFixed
  MyBase.DropDownStyle = ComboBoxStyle.DropDownList
  ' Cache the button's modified ClientRectangle (see Note)
  With _ButtonArea
    .X = Me.ClientRectangle.X - 1
    .Y = Me.ClientRectangle.Y - 1
    .Width = Me.ClientRectangle.Width + 2
    .Height = Me.ClientRectangle.Height + 2
  End With
End Sub

And now I can hook into the OnPaint event:

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
  If Me.DroppedDown Then
    ButtonRenderer.DrawButton(Me.CreateGraphics, _ButtonArea, VisualStyles.PushButtonState.Pressed)
  Else
    ButtonRenderer.DrawButton(Me.CreateGraphics, _ButtonArea, VisualStyles.PushButtonState.Normal)
  End If
  MyBase.OnPaint(e)
End Sub

Note: Yes, the _ButtonArea rectangle must be enlarged by 1 pixel to all directions (up, down, left, right), or else there will be a 1-pixel 'perimeter' around the ButtonRenderer that shows garbage. Made me crazy for awhile until I read that I must enlarge the Control's rect for ButtonRenderer.