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);
}