Create custom UI appearance for WinForms CustomButton - part 4
(All posts in this series: Post 1, Post 2, Post 3, Post 4, Post 5, Post 6, Source Code)
In this fourth part I’d like to present a custom button control. As all other controls described it has some customizable and different look but this one has an additional simple functionality - it has checked/unchecked state. When it is checked its appearance is changed.
For CustomButton control I need to implement a new appearance class with properties which describes the look of the control. This class is based on CustomBaseUIAppearance class (see my first post) and its name is CustomButtonUIAppearance. All attributes used in this class are described in my first post. So, using this class we can specify button’s border, background and fore color in the case mouse hovers the button, mouse left button is pressed (mouse down) and when it checked state is set to true. The last property here is BorderAngle it allows to specify an integer which will change button’s corner rounding. Well, do not expect to much of this because in this sample I use points to draw a polygon representing the button. And if value of BorderAngle is higher than 3 the button doesn’t look so good.
[TypeConverter(typeof(CustomBaseUIAppearanceTypeConverter))]
public class CustomButtonUIAppearance : CustomBaseUIAppearance
{
private Color _mouseHoverBorderColor;
private Color _mouseHoverBackColor;
private Color _mouseHoverForeColor;
private Color _mouseDownBorderColor;
private Color _mouseDownBackColor;
private Color _mouseDownForeColor;
private Color _checkedBorderColor;
private Color _checkedBackColor;
private Color _checkedForeColor;
private int _borderAngle;
[Description("Gets or sets button's border color when mouse hovers the button."),
Category("Custom Appearance"), DefaultValue(typeof(Color), "Black")]
public Color MouseHoverBorderColor
{
get { return _mouseHoverBorderColor; }
set
{
if (value.Equals(Color.Transparent))
{
throw new ArgumentOutOfRangeException("MouseHoverBorderColor", "Parameter cannot be set to Color.Transparent");
}
if (!_mouseHoverBorderColor.Equals(value))
{
_mouseHoverBorderColor = value;
Owner.Invalidate();
}
}
}
[Description("Gets or sets button's background color when mouse hovers the button."),
Category("Custom Appearance"), DefaultValue(typeof(Color), "White")]
public Color MouseHoverBackColor
{
get { return _mouseHoverBackColor; }
set
{
if (value.Equals(Color.Transparent))
{
throw new ArgumentOutOfRangeException("MouseHoverBackColor", "Parameter cannot be set to Color.Transparent");
}
if (!_mouseHoverBackColor.Equals(value))
{
_mouseHoverBackColor = value;
Owner.Invalidate();
}
}
}
[Description("Gets or sets button's fore color when mouse hovers the button."),
Category("Custom Appearance"), DefaultValue(typeof(Color), "Black")]
public Color MouseHoverForeColor
{
get { return _mouseHoverForeColor; }
set
{
if (value.Equals(Color.Transparent))
{
throw new ArgumentOutOfRangeException("MouseHoverForeColor", "Parameter cannot be set to Color.Transparent");
}
if (!_mouseHoverForeColor.Equals(value))
{
_mouseHoverForeColor = value;
Owner.Invalidate();
}
}
}
[Description("Gets or sets button's border color when mouse down."),
Category("Custom Appearance"), DefaultValue(typeof(Color), "Black")]
public Color MouseDownBorderColor
{
get { return _mouseDownBorderColor; }
set
{
if (value.Equals(Color.Transparent))
{
throw new ArgumentOutOfRangeException("MouseDownBorderColor", "Parameter cannot be set to Color.Transparent");
}
if (!_mouseDownBorderColor.Equals(value))
{
_mouseDownBorderColor = value;
Owner.Invalidate();
}
}
}
[Description("Gets or sets button's background color when mouse down."),
Category("Custom Appearance"), DefaultValue(typeof(Color), "White")]
public Color MouseDownBackColor
{
get { return _mouseDownBackColor; }
set
{
if (value.Equals(Color.Transparent))
{
throw new ArgumentOutOfRangeException("MouseDownBackColor", "Parameter cannot be set to Color.Transparent");
}
if (!_mouseDownBackColor.Equals(value))
{
_mouseDownBackColor = value;
Owner.Invalidate();
}
}
}
[Description("Gets or sets button's fore color when mouse down."),
Category("Custom Appearance"), DefaultValue(typeof(Color), "Black")]
public Color MouseDownForeColor
{
get { return _mouseDownForeColor; }
set
{
if (value.Equals(Color.Transparent))
{
throw new ArgumentOutOfRangeException("MouseDownForeColor", "Parameter cannot be set to Color.Transparent");
}
if (!_mouseDownForeColor.Equals(value))
{
_mouseDownForeColor = value;
Owner.Invalidate();
}
}
}
[Description("Gets or sets button's border color when its state is checked."),
Category("Custom Appearance"), DefaultValue(typeof(Color), "Black")]
public Color CheckedBorderColor
{
get { return _checkedBorderColor; }
set
{
if (value.Equals(Color.Transparent))
{
throw new ArgumentOutOfRangeException("CheckedBorderColor", "Parameter cannot be set to Color.Transparent");
}
if (!_checkedBorderColor.Equals(value))
{
_checkedBorderColor = value;
Owner.Invalidate();
}
}
}
[Description("Gets or sets button's background color when its state is checked."),
Category("Custom Appearance"), DefaultValue(typeof(Color), "White")]
public Color CheckedBackColor
{
get { return _checkedBackColor; }
set
{
if (value.Equals(Color.Transparent))
{
throw new ArgumentOutOfRangeException("CheckedBackColor", "Parameter cannot be set to Color.Transparent");
}
if (!_checkedBackColor.Equals(value))
{
_checkedBackColor = value;
Owner.Invalidate();
}
}
}
[Description("Gets or sets button's fore color when its state is checked."),
Category("Custom Appearance"), DefaultValue(typeof(Color), "Black")]
public Color CheckedForeColor
{
get { return _checkedForeColor; }
set
{
if (value.Equals(Color.Transparent))
{
throw new ArgumentOutOfRangeException("CheckedForeColor", "Parameter cannot be set to Color.Transparent");
}
if (!_checkedForeColor.Equals(value))
{
_checkedForeColor = value;
Owner.Invalidate();
}
}
}
[Description("Gets or Sets button border's angle."), Category("Custom Appearance"), DefaultValue(3)]
public int BorderAngle
{
get { return _borderAngle; }
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException("BorderAngle", "Parameter must be set to a valid integer value hihger than or equal to 0.");
}
if (_borderAngle != value)
{
_borderAngle = value;
Owner.Invalidate();
}
}
}
public CustomButtonUIAppearance(Control owner)
: base(owner)
{
_mouseHoverBorderColor = Color.Black;
_mouseHoverBackColor = Color.White;
_mouseHoverForeColor = Color.Black;
_mouseDownBorderColor = Color.Black;
_mouseDownBackColor = Color.White;
_mouseDownForeColor = Color.Black;
_checkedBorderColor = Color.Black;
_checkedBackColor = Color.White;
_checkedForeColor = Color.Black;
_borderAngle = 3;
}
}
After defining CustomButtonUIAppearance class we are ready to write down CustomButton class implementation. It overrides OnMouseEnter, OnMouseLeave, OnMouseDown and OnMouseUp (line 72 - 99). There _currentEvent field is set to appropriate value of ButtonEvents (line 200 - 208) and Invalidate() base class’ method is called in order to repaint the control after changes. OnPaint method implementation determines which colors to use based on _currentEvent (line 114 - 141) and after that draws the button (line 143 - 198). Checked state of the control is implemented via Checked property (line 40 - 54). When its value is changed an event is raised. This event named CheckedStatusChanged is declared at line 07 - 13. OnCheckedStatusChanged method is defined in order to ease us raising the event
public class CustomButton : Button
{
private bool _enableCustomAppearance;
private CustomButtonUIAppearance _customAppearance;
private bool _checked;
private ButtonEvents _currentEvent;
private EventHandler _checkedStatusChanged;
public event EventHandler CheckedStatusChanged
{
add { _checkedStatusChanged += value; }
remove { _checkedStatusChanged -= value; }
}
[Description("If true control's is drawn using custom UI appearance, otherwise it appears using standard drawing."), Category("Custom Appearance"),
DefaultValue(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public bool EnableCustomAppearance
{
get { return _enableCustomAppearance; }
set
{
if (_enableCustomAppearance != value)
{
_enableCustomAppearance = value;
if (_enableCustomAppearance)
{
AutoSize = false;
}
}
}
}
[Description("This property specifies the way of drawing the control."), Category("Custom Appearance"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public CustomButtonUIAppearance CustomAppearance
{
get { return _customAppearance; }
}
[Description("Get or sets the checked state."), Category("Custom Appearance"),
DefaultValue(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public bool Checked
{
get { return _checked; }
set
{
if (_checked != value)
{
_checked = value;
Invalidate();
OnCheckedStatusChanged(new EventArgs());
}
}
}
public CustomButton() : base()
{
_enableCustomAppearance = true;
_customAppearance = new CustomButtonUIAppearance(this);
_currentEvent = ButtonEvents.NoEvent;
_checked = false;
}
protected virtual void OnCheckedStatusChanged(EventArgs args)
{
if (_checkedStatusChanged != null)
{
_checkedStatusChanged(this, args);
}
}
protected override void OnMouseEnter(EventArgs e)
{
_currentEvent = ButtonEvents.MouseEnter;
Invalidate();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
_currentEvent = ButtonEvents.MouseLeave;
Invalidate();
base.OnMouseLeave(e);
}
protected override void OnMouseDown(MouseEventArgs mevent)
{
_currentEvent = ButtonEvents.MouseDown;
Invalidate();
base.OnMouseDown(mevent);
}
protected override void OnMouseUp(MouseEventArgs mevent)
{
_currentEvent = ButtonEvents.MouseUp;
Invalidate();
base.OnMouseUp(mevent);
}
protected override void OnPaint(PaintEventArgs pevent)
{
// Call base class' OnPaint.
base.OnPaint(pevent);
if (!_enableCustomAppearance)
{
return;
}
int offset = 1;
Color borderColor;
Color backColor;
Color foreColor;
switch (_currentEvent)
{
case ButtonEvents.MouseDown:
borderColor = _customAppearance.MouseDownBorderColor;
backColor = _customAppearance.MouseDownBackColor;
foreColor = _customAppearance.MouseDownForeColor;
break;
case ButtonEvents.MouseEnter:
case ButtonEvents.MouseUp:
borderColor = _customAppearance.MouseHoverBorderColor;
backColor = _customAppearance.MouseHoverBackColor;
foreColor = _customAppearance.MouseHoverForeColor;
break;
default: // MouseLeave and NoEvent
if (_checked)
{
borderColor = _customAppearance.CheckedBorderColor;
backColor = _customAppearance.CheckedBackColor;
foreColor = _customAppearance.CheckedForeColor;
}
else
{
borderColor = _customAppearance.BorderColor;
backColor = _customAppearance.BackColor;
foreColor = ForeColor;
}
break;
}
Graphics graphics = pevent.Graphics;
graphics.Clear(BackColor);
// get Text measure according to selected Font
SizeF stringMeasure = graphics.MeasureString(Text, Font);
// Set graphics object to paint nice using antialias.
if (_customAppearance.EnableAntiAlias)
{
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
}
// ++ Calculate drawing string offsets ++
int leftOffset = (int)(ClientRectangle.Width - stringMeasure.Width) / 2;
int topOffset = (int)(ClientRectangle.Height - stringMeasure.Height) / 2 + offset;
if (leftOffset < 0)
{
leftOffset = offset + Padding.Left;
}
else
{
leftOffset += Padding.Left;
}
if (topOffset < 0)
{
topOffset = offset + Padding.Top;
}
else
{
topOffset += Padding.Top;
}
// — Calculate drawing string offsets –
int drawingAngleSize = _customAppearance.BorderAngle + _customAppearance.BorderThicness;
int drawingWidth = Width - _customAppearance.BorderThicness;
int drawingHeight = Height - _customAppearance.BorderThicness;
// Points used to draw and fill button rectangle
Point[] points = new Point[9]
{
new Point(drawingAngleSize, _customAppearance.BorderThicness),
new Point(drawingWidth - _customAppearance.BorderAngle - _customAppearance.BorderThicness, _customAppearance.BorderThicness),
new Point(drawingWidth - _customAppearance.BorderThicness, drawingAngleSize),
new Point(drawingWidth - _customAppearance.BorderThicness, drawingHeight - _customAppearance.BorderAngle),
new Point(drawingWidth - _customAppearance.BorderAngle - _customAppearance.BorderThicness, drawingHeight),
new Point(drawingAngleSize, drawingHeight),
new Point(_customAppearance.BorderThicness, drawingHeight - _customAppearance.BorderAngle),
new Point(_customAppearance.BorderThicness, drawingAngleSize),
new Point(drawingAngleSize, _customAppearance.BorderThicness)
};
graphics.FillPolygon(new SolidBrush(backColor), points);
graphics.DrawPolygon(new Pen(borderColor, _customAppearance.BorderThicness), points);
graphics.DrawString(Text, Font, new SolidBrush(foreColor), new Point(leftOffset, topOffset));
}
private enum ButtonEvents
{
NoEvent,
MouseEnter,
MouseLeave,
MouseDown,
MouseUp
}
}
Here is a picture of how CustomButton class configuration properties look in VS designer property grid.
You can see how button looks. A button with checked state set to true is depicted at the bottom left. Next right to it is the one with checked state false and normal visualization. At the bottom left you can see mouse hover appearance and next right to it when mouse left/right button is pressed down.


July 11th, 2008 at 4:46 pm
[...] Create custom UI appearance for WinForms CustomButton - part 4 [...]