Painting A Disabled Button

It is always the “simplest” task that ends up taking the longest.  Over the last few months, I’ve encountered this phenomenon several times with the .Net Windows Forms Controls.

One of the applications I was working on required buttons to be these “lovely” vibrant colors (sort of pink and blue with black text).  I didn’t care for the appearance, but the requirement wasn’t going to change.  However, things started to look really bad (and even confusing to the user) once there were a few forms created that needed to have disabled buttons.  The problem is that the button control’s fore color is actually dictated by its back color when it is disabled.  When you set the ForeColor of a disabled button, the new color is ignored.  The color is always a darker tint of the back color.  And, that made it appear as if the button was still enabled.

The desire became to make the text grey out when the button was disabled (the same fore color as a button that doesn’t have a custom back color).  So, I set out to make that happen.

After some digging on the web, I soon ended up with an override of OnPaint.  (And, I quickly realized that this was going to be a pain.)

First step, I needed to skip the custom drawing when the control was enabled.

if (Enabled)
    base.OnPaint(pevent);

 

Second step, fill the background with the correct color.

 

//Draw a background just inside the borders of the control
using
(SolidBrush brush = new SolidBrush(BackColor))
{
    pevent.Graphics.FillRectangle(brush, ClientRectangle.X + 2,
                    ClientRectangle.Y + 2, ClientRectangle.Width – 2, ClientRectangle.Height – 2);
}

 

So far, this was looking OK.  Except now I saw that I had to align the text appropriately.

 

//Draw the text
//Need to properly align the text when drawing it
using (StringFormat sf = new StringFormat())
{
    if (TextAlign == ContentAlignment.BottomCenter ||
        TextAlign == ContentAlignment.BottomLeft ||
        TextAlign == ContentAlignment.BottomRight)
    {
        sf.LineAlignment = StringAlignment.Far;
    }
    else if (TextAlign == ContentAlignment.MiddleCenter ||
        TextAlign == ContentAlignment.MiddleLeft ||
        TextAlign == ContentAlignment.MiddleRight)
    {
        sf.LineAlignment = StringAlignment.Center;
    }
    else if (TextAlign == ContentAlignment.TopCenter ||
        TextAlign == ContentAlignment.TopLeft ||
        TextAlign == ContentAlignment.TopRight)
    {
        sf.LineAlignment = StringAlignment.Near;
    }

    if (TextAlign == ContentAlignment.BottomRight ||
        TextAlign == ContentAlignment.MiddleRight ||
        TextAlign == ContentAlignment.TopRight)
    {
        sf.Alignment = RightToLeft == RightToLeft.Yes ? StringAlignment.Near : StringAlignment.Far;
    }
    else if (TextAlign == ContentAlignment.BottomCenter ||
        TextAlign == ContentAlignment.MiddleCenter ||
        TextAlign == ContentAlignment.TopCenter)
    {
        sf.Alignment = StringAlignment.Center;
    }
    else if (TextAlign == ContentAlignment.BottomLeft ||
        TextAlign == ContentAlignment.MiddleLeft ||
        TextAlign == ContentAlignment.TopLeft)
    {
        sf.Alignment = RightToLeft == RightToLeft.Yes ? StringAlignment.Far : StringAlignment.Near;
    }

 

    sf.FormatFlags = StringFormatFlags.DisplayFormatControl;
    pevent.Graphics.DrawString(Text, Font, Brushes.Gray, ClientRectangle, sf);

 

Finally, I needed to draw the button’s border.

 

//Draw the border
using (Pen pen = new Pen(SystemColors.InactiveBorder, 1))
{
    
//Use draw lines so we can draw the “rounded” border
    pevent.Graphics.DrawLines(pen, GetBorderPoints(
1, 1, Width – 2, Height – 2));
}


//This is a painting helper so we can draw a rounded border
private Point[] GetBorderPoints(int left, int top, int width, int height)
{
    Point[] points =
    {
        
new Point(left + 1   , top  ),
        
new Point(width – 1 , top  ),
        
new Point(width – 1 , top + 1  ),
        
new Point(width   , top + 1  ),
        
new Point(width   , height – 1),
        
new Point(width – 1 , height – 1),
        
new Point(width – 1 , height  ),
        
new Point(left + 1   , height  ),
        
new Point(left + 1   , height – 1),
        
new Point(left   , height – 1),
        
new Point(left   , top + 1  ),
        
new Point(left + 1   , top + 1  )
    };
return points;
}

 

At this point, I really thought I was going to be able to achieve the desired result.  But, after a few tests, I noticed the mnemonic & character was showing up in the text when the button was in its disabled state.  That resulted in an override of the Text property.

 

 

//This is needed in conjunction with the OnPaint override to draw the control with gray disabled text
//   This strips out the mnemonic’s & character so the OnPaint override doesn’t include it in the display
private string _disabledText = string.Empty;
private string _enabledText = string.Empty;
public override string Text
{
    
get
    {
        
if (Enabled)
            
return _enabledText;
        
else
            
return _disabledText;
    }
    
set
    {
        _enabledText = value;

 

        //strip out the mnemonic’s ampersand
        
int mnemonicIndex = -1;
        
int index = 0;
        
foreach (char c in _enabledText)
        {
            
if (IsMnemonic(c, _enabledText))
            {
                mnemonicIndex = index;
                
break;
            }

 

            index++;
        }

 

        _disabledText = _enabledText;

        if (mnemonicIndex > 0)
            _disabledText = _enabledText.Remove(mnemonicIndex –
1, 1);base.Text = value;
    }
}

 

Now, I had a control that was really close to the desired result, with one big “but”.  The control was painting a small, but quite visible, piece of border at its upper left.  And, I was really running out of allocated time for would should have been (or so you would think) such a small task.  So, rather than invest the continued effort to remove the unwanted border, I abandoned the whole thing, and approached it from another angle — which probably gave a nicer result in the end.

 

What I did was to simply set the background color to its default SystemColors.Control. This gave the control a definite disabled appearance that could easily be understood by the user, and was considerably simpler to implement.

 

protected override void OnEnabledChanged(EventArgs e)
{
    
//At least with this, the control won’t have brightly colored text when it is disabled
    
//The default behavior draws the disabled text in the same base color as the background
    
//    just shaded slightly darker — that’s why it looks bad for these buttons

    if (Enabled)
        
base.BackColor = _cachedBackColor;
    
else
        
base.BackColor = SystemColors.Control;base.OnEnabledChanged(e);
}

private Color _cachedBackColor = Color.Empty;
public new Color BackColor
{
    
get
    {
        
return _cachedBackColor;
    }
    
set
    {
        _cachedBackColor = value;
        
if (Enabled)
            
base.BackColor = value;
    }
}
  

 

The result looked like the following:

Disabled Windows Forms Button Control Enabled Windows Forms Button

Rss Feed