Override DrawItem of ComboBox

10,830

Solution 1

Here you go:

public class myCombo : ComboBox
{
    // expose properties as needed
    public Color SelectedBackColor{ get; set; }

    // constructor
    public myCombo()
    {
        DrawItem += new DrawItemEventHandler(DrawCustomMenuItem);
        DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
        SelectedBackColor= Color.LightSeaGreen;
    }

    protected  void DrawCustomMenuItem(object sender, DrawItemEventArgs e)
    {
        e.DrawBackground();
         // a dropdownlist may initially have no item selected, so skip the highlighting:
        if (e.Index >= 0) 
        {  
          Graphics g = e.Graphics;
          Brush brush = ((e.State & DrawItemState.Selected) == DrawItemState.Selected) ?
                         new SolidBrush(SelectedBackColor) : new SolidBrush(e.BackColor);
          Brush tBrush = new SolidBrush(e.ForeColor);

          g.FillRectangle(brush, e.Bounds);
          e.Graphics.DrawString(this.Items[e.Index].ToString(), e.Font,
                     tBrush, e.Bounds, StringFormat.GenericDefault);
          brush.Dispose();
          tBrush.Dispose();
        }
        e.DrawFocusRectangle();
    }
}

You may consider exposing more Properties as you expand your customziation, so you can change them for each instance when you want to..

Also don't forget to dispose GDI objects you create, like brushes and pens!

Edit: Just noticed that BackColor would hide the original property. Changed it to SelectedBackColor, which actually says what it is!

Edit 2: As Simon noted in the comments, there is a HasFlag method and so as of .Net 4.0 one can also write:

      Brush brush = ((e.State.HasFlag(DrawItemState.Selected) ?

which is a little clearer and shorter.

Edit 3: Actually for drawing onto controls the use of TextRenderer.DrawText is recommended over graphics.DrawString..

Solution 2

Thanks for this helpful post. I made one small enhancement that others may find useful.

When the ComboBox has its DisplayMember property set to access a specific property of the displayed items, ToString() may not give the expected text. The fix for this is to use:

    GetItemText(Items[e.Index])

to retrieve the required text in the call to DrawString().

Share:
10,830
JackMini36
Author by

JackMini36

VR/AR dev by Day. Indie game dev by night.

Updated on June 04, 2022

Comments

  • JackMini36
    JackMini36 almost 2 years

    I changed the highlight color of various of the controls, and I am planning to make more changes. So I though is better to create my own controls and reuse them instead of making the changed for each and every one of them.

    I created a new user control, and inherited from System.Windows.Forms.ComboBox. The problem is I cannot find a way to override onDraw like I would for onClick.

    So how I would go and override it? Here is the code I used for each control onDraw event

    public void comboMasterUsers_DrawItem(object sender, DrawItemEventArgs e)
        {
            e.DrawBackground();
    
            Graphics g = e.Graphics;
            Brush brush = ((e.State & DrawItemState.Selected) == DrawItemState.Selected) ?
                          Brushes.LightSeaGreen : new SolidBrush(e.BackColor);
    
            g.FillRectangle(brush, e.Bounds);
            e.Graphics.DrawString(comboMasterUsers.Items[e.Index].ToString(), e.Font,
                     new SolidBrush(e.ForeColor), e.Bounds, StringFormat.GenericDefault);
    
            e.DrawFocusRectangle();
        }
    

    Thanks!

  • JackMini36
    JackMini36 almost 10 years
    Thanks, it works. But there is one problem. When I change the DropDownStyle to DropDownList, there is an error: InvalidArgument =value of -1 is not valid for index
  • TaW
    TaW almost 10 years
    see my small correction wrt tBrush! GDI objects have a hideous tendency to silently leak..
  • TaW
    TaW almost 10 years
    And another small correction ;-) I didn't notice the warning but now it looks OK..
  • TaW
    TaW almost 10 years
    To make a long, sad story short: Brushes, Pens and Bitmaps are special. They are not normal c# objects, which the GC collects; they are stored separately and it is your responsibilty to dispose of them. A GDI heritage from the 90s..
  • JackMini36
    JackMini36 almost 10 years
    oh I see.. Thx for the info!
  • Simon Jackson
    Simon Jackson over 8 years
    In .Net 4 and above, I think it is cleaner to write "e.State.HasFlag(DrawItemState.Selected)" instead of "e.State & DrawItemState.Selected) == DrawItemState.Selected"
  • Elmue
    Elmue over 5 years
    Instead of disposing the Brushes you could use the using(...) clause. All your background drawing with SelectedBackColor and FillRectangle() is unnecessary because this is already done in e.DrawBackground();
  • Cheva
    Cheva over 5 years
    Instead of "this.Items[e.Index].ToString()" use : var svalue = string.Empty; var pi = Items[e.Index].GetType().GetProperty(DisplayMember); if (pi != null) svalue = pi.GetValue(Items[e.Index]).ToString();
  • Cheva
    Cheva over 5 years
    Get item text: string svalue = this.GetItemText(SelectedItem);