views:

312

answers:

2

I'm working on a custom ComboBox for a project, and have the basic painting working. One of the remaining things to do is make it look like the default ComboBox in Vista and 7, when the DropDownStyle is set to DropDownList (looks like a button instead of a regular ComboBox).

I read much about VisualStyleRenderer, read ComboBox.cs and ComboBoxRenderer.cs, and tried to get it working. It works in the sense of, it does something. It just doesn't do what it's supposed to do.

I also found an article about Vista themed Owner-Drawn Buttons, but it is in C++ (which would be a challenge on its own for me) and it seems like an awful lot of code.

If anyone could supply either VB or C# code, that would be great. Below is my current code.

Public Sub New()
    DrawMode = Windows.Forms.DrawMode.OwnerDrawFixed

    ItemHeight *= 2

    DropDownStyle = ComboBoxStyle.DropDownList
End Sub

Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
    If e.Index = -1 Then
        Return
    End If

    If Not (e.State = DrawItemState.Selected) Then
        e.DrawBackground()
    End If

    Dim brsh As Brush
    If (e.State And DrawItemState.Focus) = DrawItemState.Focus And (e.State And DrawItemState.Selected) = DrawItemState.Selected Then
        brsh = SystemBrushes.HighlightText
    Else
        brsh = SystemBrushes.ControlText
    End If

    e.DrawFocusRectangle()

    e.Graphics.DrawString(Items(e.Index), e.Font, brsh, e.Bounds)
End Sub
+1  A: 

Yes, this is known behavior for ComboBox. Appcompat no doubt, it switches to legacy rendering mode when owner draw is enabled. There is no known fix for this. Also covered in this SO question.

I have a solution however. There's another native combo box control in Windows, named ComboBoxEx. It is largely forgotten and not wrapped by Windows Forms. It is a common control, designed to easily draw images in the dropdown list. It always has owner draw turned on. As luck would have it, this control doesn't flip the appcompat switch, just what you need.

Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form. Alternatively, you'd probably just want to copy the CreateParams override into your own control.

Public Class ComboBoxEx
  Inherits ComboBox

  Public Sub New()
    MyBase.DropDownStyle = ComboBoxStyle.DropDownList
  End Sub

  Protected Overrides ReadOnly Property CreateParams As System.Windows.Forms.CreateParams
    Get
      Dim parms As CreateParams = MyBase.CreateParams
      parms.ClassName = "ComboBoxEx32"
      REM turn off WS_EX_CLIENTEDGE
      parms.ExStyle = parms.ExStyle And Not &H200  
      Return parms
    End Get
  End Property

  Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
    REM map WM_DRAWITEM
    If m.Msg = &H2B Then m.Msg = &H202B
    MyBase.WndProc(m)
  End Sub

End Class

Beware that I did not do any extensive testing on this, I'm not 100% sure that the Window Forms ComboBox wrapper class is compatible enough with the native control. I also took a shortcut on the requirement to call InitCommonControlsEx, it won't work on a machine that has visual styles turned off. I suspect you might run into a few surprises. The SDK docs for this control are here.

Hans Passant
Thanks. This draws the control correctly, but now it doesn't seem to call OnDrawItem anymore.I'll try to find out what causes this.
Stijn
Yup, surprise #1. I added an override for WndProc to the code snippet to fix that.
Hans Passant
That doesn't seem to do anything, unfortunately.
Stijn
A: 

hi,

I'm trying to get this to work too. I have converted your solution to c# and it 'kind of' works.

It fixes the drawing issue, and triggers the OnDrawItem event, however, the DrawItemEventArgs always had an Index of -1.

Its difficult to tell, but I don't think any of the other events I have overridden are being fired.

Could you please explain what the WndProc is doing, and do I need to convert other messages for the other events?

Cesare

Cesare