Search code examples
c#winformsgraphicscomboboxgdi+

How can I use the ControlPaint.DrawBorder method to modifiy the border color of a ComboBox?


I have a requirement to set the border color of the Combobox control.
I've searched Google for a sample code that was implementing ControlPaint.Drawborder.
What I found is not ideal. The color of the border is not modified.
In the code below, you'll see two key functions, WndProc and PaintControlBorder(), which are used to receive the underlying windows events and handle them.
The latter is used to draw the border of the control. After debugging with Visual Studio, I found that the code in the PaintControlBorder() function was executed.

Why is the border color not changing?

[ToolboxItem(true)]
public class ModifiedComboBox : ComboBox
{
    new public System.Windows.Forms.DrawMode DrawMode { get; set; }
    public Color HighlightColor { get; set; } //set select item highlight color 
    private IntPtr hDC;
    private Graphics gdc;
    private const int WM_ERASEBKGND = 0x14; // some windows message id 
    private const int WM_PAINT = 0xF;
    private const int WM_NC_PAINT = 0x85;
    private const int WM_PRINTCLIENT = 0x318;
    private const int WM_MOUSEHOVER = 0x2A1;
    private const int WM_MOUSELEAVE = 0x2A3;

    [DllImport("user32")]
    private static extern IntPtr GetDC(IntPtr hWnd);
    private Rectangle rectale;

    public ModifiedComboBox()
    {
        hDC = GetDC(Handle);
        gdc = Graphics.FromHdc(hDC);
        SetStyle(ControlStyles.Selectable, false);
        base.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
        this.HighlightColor = Color.Gray;
        this.DrawItem += new DrawItemEventHandler(AdvancedComboBox_DrawItem); //t bind draw item handler

        rectale = new Rectangle(0, 0, Width, Height);
    }

    public void AdvancedComboBox_DrawItem(object sender, DrawItemEventArgs e)
    {
        if (e.Index < 0)
            return;

        ComboBox combo = sender as ComboBox;
        if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
        {
            e.Graphics.FillRectangle(new SolidBrush(HighlightColor), e.Bounds);
            //e.Graphics.DrawString(combo.Items[e.Index].ToString(), e.Font, new SolidBrush(combo.BackColor), new Point(e.Bounds.X, e.Bounds.Y));

            e.Graphics.DrawString(combo.Items[e.Index].ToString(), e.Font, new SolidBrush(combo.ForeColor), new Point(e.Bounds.X, e.Bounds.Y));
        }
        else
        {
            e.Graphics.FillRectangle(new SolidBrush(combo.BackColor), e.Bounds);
            e.Graphics.DrawString(combo.Items[e.Index].ToString(), e.Font, new SolidBrush(combo.ForeColor), new Point(e.Bounds.X, e.Bounds.Y));
        }
        e.DrawFocusRectangle();
    }

    protected override void WndProc(ref Message m)// windows message handler
    {
        if (this.DropDownStyle == ComboBoxStyle.Simple)
        {
            base.WndProc(ref m);
            return;
        }

        switch (m.Msg)
        {
            case WM_NC_PAINT:
                break;
            case WM_PAINT:
                base.WndProc(ref m);

                PaintControlBorder();
                break;
            default:
                base.WndProc(ref m);
                break;
        }
    }
    private void PaintControlBorder()
    {
            ControlPaint.DrawBorder(gdc, this.ClientRectangle, Color.Orange, ButtonBorderStyle.Solid);
    }
}

Solution

  • In Windows Forms, you never store the Graphics object of a Control.
    You can use the one provided by one of the Graphics methods (Paint, DrawItem etc.) or, like in this case, you create a new one when required and you dispose of it right after.
    In this example, implicitly, enclosing the Graphics object creation in an using block.

    This is a simple example, using ControlPaint.DrawBorder, that show how draw a border around a ComboBox control, using a Graphics object derived from the control Handle (Graphics.FromHwnd(this.Handle)) on a WM_PAINT message received overriding the control's WndProc.

    The sample Custom Control exposes a public Property, BorderColor, which allows to change the color of the border.

    using System.ComponentModel;
    using System.Drawing;
    using System.Security.Permissions;
    using System.Windows.Forms;
    
    [DesignerCategory("Code")]
    class ComboSimpleBorder : ComboBox
    {
        private const int WM_PAINT = 0xF;
        private Color m_BorderColor = Color.Red;
    
        public ComboSimpleBorder() { }
    
        [Browsable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [EditorBrowsable(EditorBrowsableState.Always), Category("Appearance")]
        [Description("Get or Set the Color of the Control's border")]
        [DefaultValue(typeof(Color), "Red")]
        public Color BorderColor 
        {
            get => m_BorderColor;
            set { m_BorderColor = value; Invalidate(); }
        }
    
        protected override void WndProc(ref Message m) {
            base.WndProc(ref m);
            if (m.Msg == WM_PAINT) {
                using (var g = Graphics.FromHwnd(this.Handle)) {
                    ControlPaint.DrawBorder(g, ClientRectangle, m_BorderColor, ButtonBorderStyle.Solid);
                }
                m.Result = IntPtr.Zero;
            }
        }
    }