Custom WinForms ComboBox Flickers in Windows 11
17:39 21 Apr 2025

I work on a Visual Studio extension. I have been testing the extension on version 17.13 of Visual Studio with .NET Framework version 4.8

I have a custom WinForms ComboBox which I repaint by intercepting WM_PAINT in WndProc. I first call base.WndProc. Then I paint the background, border and dropdown button, and when the ComboBox is not editable, I also paint the text.

On Windows 10, the control works reasonably well and there is no flickering. On Windows 11, when I hover over the control, it flickers a fair amount, especially when I hover back and forth near the dropdown button.

I have tried the following:

Intercept wm_erasebkgnd

  • This seems to help a little, but not much.

Set UserPaint, OptimizedDoubleBuffer, AllPaintingInWmPaint

  • This did not seem to help, and I also seem unable to render the text for an editable ComboBox

Override CreateParams and set ws_ex_composited

  • I did this in the custom ComboBox itself, not the form, because it is just the specific hovered control that is flickering
  • This causes an unknown error

I have also looked at some of the issues on here and on the GitHub forum and nothing has helped so far.

Do you have any advice/know what might be causing this issue?

Thanks.

Edit: Here is the code:

public class SimpleThemedComboBox : ComboBox
{
private const int WM_PAINT = 0xF;
private const int WM_CTLCOLOREDIT = 0x0133;
private const int WM_CTLCOLORSTATIC = 0x0138;
private const int WM_ERASEBKGND = 0x0014;
private bool _isHovered = false;
private int _buttonWidth;
private IntPtr _backgroundBrushHandle;

public SimpleThemedComboBox()
{
    //this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
    //        ControlStyles.AllPaintingInWmPaint |
    //        ControlStyles.UserPaint, true);
    //this.UpdateStyles();
    this.DrawMode = DrawMode.OwnerDrawFixed;
    _buttonWidth = SystemInformation.HorizontalScrollBarArrowWidth;
    //DoubleBuffered = true;
    //this.FlatStyle = FlatStyle.Flat;
}

/// 
/// Dispose
/// Dispose of backgroundBrushHandle IntPtr used for WM_CTLCOLOREDIT and WM_CTLCOLORSTATIC
/// 
protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        if (_backgroundBrushHandle != IntPtr.Zero)
        {
            DeleteObject(_backgroundBrushHandle);
            _backgroundBrushHandle = IntPtr.Zero;
        }
    }
    base.Dispose(disposing);
}

/// 
/// OnDrawItem
/// Draw each item in the dropdown list
/// 
protected override void OnDrawItem(DrawItemEventArgs e)
{
    base.OnDrawItem(e);
    Color backgroundColor;
    Color textColor;
    // get colors for item hovered/selected
    if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
    {
        backgroundColor = Color.DarkBlue;
        textColor = Color.White;
    }
    else
    {
        backgroundColor = Color.DarkGray;
        textColor = Color.White;
    }
    // draw background
    using (SolidBrush br = new SolidBrush(backgroundColor))
    {
        e.Graphics.FillRectangle(br, e.Bounds);
    }
    // draw text
    if (this.Items != null && this.Items.Count > 0 && e.Index >= 0)
    {
        var item = this.Items[e.Index];
        string itemText = item.ToString();
        TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter;
        TextRenderer.DrawText(e.Graphics, itemText, this.Font, e.Bounds, textColor, flags);
    }
}

/// 
/// WndProc
/// Override WndProc to intercept WM_PAINT, WM_CTLCOLOREDIT, WM_CTLCOLORSTATIC
/// WM_PAINT - paint the control
/// WM_CTLCOLOREDIT - message sent to paint/theme the textbox editing control 
///   in an editable combobox
/// WM_CTLCOLORSTATIC - message sent to paint/theme the textbox editing control 
///   in a disabled/readonly combobox
/// 
protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_CTLCOLOREDIT || m.Msg == WM_CTLCOLORSTATIC)
    {
        IntPtr hdc = m.WParam;
        // Set back and text color
        HandleColorEditStatic(hdc);
        // get a pointer to a background brush to be returned
        _backgroundBrushHandle = CreateSolidBrush(ColorTranslator.ToWin32(BackColor));
        m.Result = _backgroundBrushHandle;
        return;
    }
    // ignore WM_ERASEBKGND to ease flickering
    if (m.Msg == WM_ERASEBKGND)
    {
        m.Result = (IntPtr)1;
        return;
    }
    base.WndProc(ref m);
    if (m.Msg == WM_PAINT)
    {
        using (PaintEventArgs pe = new PaintEventArgs(Graphics.FromHwnd(this.Handle), this.ClientRectangle))
        {
            if (DropDownStyle == ComboBoxStyle.DropDownList)
            {
                DrawDropDown(pe.Graphics);
            }
            else
            {
                DrawComboBox(pe.Graphics);
            }
        }
    }
}

/// 
/// DrawDropDown
/// Draw the DropDown itself - background, border, button, and text
/// DropDowns are comboboxes that are not editable
/// Do not draw button and separator, unless pressed
/// 
private void DrawDropDown(Graphics g)
{
    //Graphics g = Graphics.FromHwnd(this.Handle);
    // set colors and standard button state
    Color backgroundColor;
    Color borderColor;
    Color foreColor;
    Color separatorColor;
    Color glyphColor;
    Color glyphBackColor;
    if (!Enabled)
    {
        // set colors
    }
    else if (DroppedDown)
    {
        // set colors
    }
    else if (_isHovered)
    {
        // set colors
    }
    else
    {
        // set colors
    }
    backgroundColor = Color.DarkGray;
    borderColor = Color.Blue;
    foreColor = Color.White;
    glyphColor = Color.White;
    glyphBackColor = backgroundColor;
    separatorColor = glyphBackColor;
    // get rectangle for drawing
    Rectangle rect = new Rectangle(0, 0, Width, Height);
    // draw background
    using (SolidBrush br = new SolidBrush(backgroundColor))
    {
        g.FillRectangle(br, rect);
    }
    // draw text
    if (this.Items != null && this.Items.Count > 0 && this.SelectedItem != null)
    {
        TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter;
        TextRenderer.DrawText(g, SelectedItem.ToString(), this.Font, rect, foreColor, flags);
    }
    // draw dropdown button if hovered or pressed
    Rectangle dropdownRect = new Rectangle(Width - _buttonWidth, 0, _buttonWidth - 1, Height - 1);
    if (_isHovered || DroppedDown)
    {
        // draw background
        using (SolidBrush br = new SolidBrush(glyphBackColor))
        {
            g.FillRectangle(br, dropdownRect);
        }
        // draw separator
        using (Pen p = new Pen(separatorColor))
        {
            g.DrawLine(p, new Point(dropdownRect.Left, dropdownRect.Top), new Point(dropdownRect.Left, dropdownRect.Bottom));
        }
    }
    // draw arrow
    Point middle = new Point(dropdownRect.Left + (dropdownRect.Width / 2),
      dropdownRect.Top + (dropdownRect.Height / 2));
    Point[] arrow = new Point[]
    {
    new Point(middle.X - 3, middle.Y - 2),
    new Point(middle.X + 4, middle.Y - 2),
    new Point(middle.X, middle.Y + 2)
    };
    using (SolidBrush br = new SolidBrush(glyphColor))
    {
        g.FillPolygon(br, arrow);
    }
    // draw border last
    using (Pen p = new Pen(borderColor))
    {
        Rectangle borderRect = new Rectangle(0, 0, Width - 1, Height - 1);
        g.DrawRectangle(p, borderRect);
    }
}

/// 
/// DrawComboBox
/// Draw an editable ComboBox itself - background, border, button, and text
/// Draw separator and button explicitly
/// Do not draw text - handled by system/DrawItem
/// 
private void DrawComboBox(Graphics g)
{
    //Graphics g = Graphics.FromHwnd(this.Handle);
    // set colors and standard button state
    Color backgroundColor;
    Color borderColor;
    Color foreColor;
    Color glyphColor;
    Color glyphBackColor;
    Color separatorColor;
    if (!Enabled)
    {
        // set colors
    }
    else if (DroppedDown)
    {
        // set colors
    }
    else if (_isHovered || Focused)
    {
        // set colors
    }
    else
    {
        // set colors
    }
    backgroundColor = Color.DarkGray;
    borderColor = Color.Blue;
    foreColor = Color.White;
    glyphColor = Color.White;
    glyphBackColor = backgroundColor;
    separatorColor = glyphBackColor;
    // get rectangle for drawing
    Rectangle rect = new Rectangle(0, 0, Width, Height);
    // draw background
    using (SolidBrush br = new SolidBrush(backgroundColor))
    {
        g.FillRectangle(br, rect);
    }
    // draw text
    //if (this.Items != null && this.Items.Count > 0 && this.SelectedItem != null)
    //{
    //  TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter;
    //  TextRenderer.DrawText(g, SelectedItem.ToString(), this.Font, rect, foreColor, flags);
    //}
    // draw dropdown button
    Rectangle dropdownRect = new Rectangle(Width - _buttonWidth, 0, _buttonWidth - 1, Height - 1);
    // draw background
    using (SolidBrush br = new SolidBrush(glyphBackColor))
    {
        g.FillRectangle(br, dropdownRect);
    }
    // draw separator
    using (Pen p = new Pen(separatorColor))
    {
        g.DrawLine(p, new Point(dropdownRect.Left, dropdownRect.Top), new Point(dropdownRect.Left, dropdownRect.Bottom));
    }
    // draw arrow
    Point middle = new Point(dropdownRect.Left + (dropdownRect.Width / 2),
      dropdownRect.Top + (dropdownRect.Height / 2));
    Point[] arrow = new Point[]
    {
    new Point(middle.X - 3, middle.Y - 2),
    new Point(middle.X + 4, middle.Y - 2),
    new Point(middle.X, middle.Y + 2)
    };
    using (SolidBrush br = new SolidBrush(glyphColor))
    {
        g.FillPolygon(br, arrow);
    }
    // draw border last
    using (Pen p = new Pen(borderColor))
    {
        Rectangle borderRect = new Rectangle(0, 0, Width - 1, Height - 1);
        g.DrawRectangle(p, borderRect);
    }
}

/// 
/// HandleColorEditStatic
/// Set a text and back color to be used for the editing control in response to WM_CTLCOLOREDIT and WM_CTLCOLORSTATIC
/// 
private void HandleColorEditStatic(IntPtr handle)
{
    if (!Enabled)
    {
        // set colors
    }
    else if (DroppedDown)
    {
        // set colors
    }
    else if (_isHovered || Focused)
    {
        // set colors
    }
    else
    {
        // set colors
    }
    BackColor = Color.DarkGray;
    ForeColor = Color.White;
    SetTextColor(handle, ColorTranslator.ToWin32(ForeColor));
    SetBkColor(handle, ColorTranslator.ToWin32(BackColor));
}

// override mouse enter and leave to set isHovered
protected override void OnMouseEnter(EventArgs e)
{
    _isHovered = true;
    base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
    _isHovered = false;
    base.OnMouseLeave(e);
}

//protected override CreateParams CreateParams
//{
//  get
//  {
//    CreateParams cp = base.CreateParams;
//    cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
//    return cp;
//  }
//}

/*
 * Import Win32 functions to resolve WM_CTLCOLOREDIT and WM_CTLCOLORSTATIC
 */
[DllImport("gdi32.dll")]
private static extern int SetTextColor(IntPtr hdc, int color);

[DllImport("gdi32.dll")]
private static extern int SetBkColor(IntPtr hdc, int color);

[DllImport("gdi32.dll")]
private static extern IntPtr CreateSolidBrush(int color);

[DllImport("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject);

}
c# visual-studio winforms windows-11