Create custom UI appearance for WinForms CheckBox - part 2
By ganton ~ July 8th, 2008. Filed under: WinForms.
(All posts in this series: Post 1, Post 2, Post 3, Post 4, Post 5, Post 6, Source Code)
In part 1 I described to classes that we need in order to be able to develop WinForms controls customizable from Visual Studio’s (VS) designer. Here in this post I’ll provide an implementation of custom CheckBox control. It will have custom UI appearance and it will derive from CheckBox.
First of all we will develop a class that will hold UI appearance information needed to customize our check box. Its name is CustomCheckBoxUIAppearance and it derives from CustomBaseUIAppearance. There is nothing new in it instead of its properties :). Attributes used are described in my first post. Here’s its implementation.
[TypeConverter(typeof(CustomBaseUIAppearanceTypeConverter))]
public class CustomCheckBoxUIAppearance : CustomBaseUIAppearance
{
private Color _checkedBorderColor;
private Color _checkedBackColor;
private Color _tickColor;
private int _tickThickness;
[Description("Gets or Sets check box's border color when checked. It can not be set to Color.Transparent."),
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 check box's background color when checked. It can not be set to Color.Transparent."),
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 check box's tick color. It can not be set to Color.Transparent."), Category("Custom Appearance"),
DefaultValue(typeof(Color), "Black")]
public Color TickColor
{
get { return _tickColor; }
set
{
if (value.Equals(Color.Transparent))
{
throw new ArgumentOutOfRangeException("TickColor", "Parameter cannot be set to Color.Transparent");
}
if (!_tickColor.Equals(value))
{
_tickColor = value;
Owner.Invalidate();
}
}
}
[Description("Gets or Sets check box's tick thickness."), Category("Custom Appearance"), DefaultValue(1)]
public int TickThickness
{
get { return _tickThickness; }
set
{
if (value < 1)
{
throw new ArgumentOutOfRangeException("TickThickness", "Parameter must be set to a valid integer value hihger than 0.");
}
if (_tickThickness != value)
{
_tickThickness = value;
Owner.Invalidate();
}
}
}
public CustomCheckBoxUIAppearance(Control owner)
: base(owner)
{
_checkedBorderColor = Color.Black;
_checkedBackColor = Color.White;
_tickColor = Color.Black;
_tickThickness = 1;
}
}
Next, we will develop CustomCheckBox class. It derives from CheckBox class (line 2). It contains two properties. The first one is EnableCustomAppearance (line 9). When it is set to true custom UI appearance will take an effect, otherwise standard CheckBox appearance will be used. It is decorated with several attributes I’ve described in my old post. The new attribute is DesignerSerializationVisibilityAttribute. It accepts a parameter of type DesignerSerializationVisibility. In our case we set it to Visible. Every change we made in property grid will reflect MyForm.Designer.cs file. DesignerSerializationVisibility members:
Member name Description Hidden The code generator does not produce code for the object. Visible The code generator produces code for the object. Content The code generator produces code for the contents of the object, rather than for the object itself.
The second property is CustomAppearance. It is an instance of CustomCheckBoxUIAppearance class and it is decorated with attributes described above. [Update 07/09/2008: But note Content enum value is provided to DesignerSerializationVisibilityAttribute instead of Visible. That is why we need code generator to produce code for the object contents, rather that for the objec itself as it is with EnableCustomAppearance.] Most importent method in our implementation is OnPaint method. Fisrt of all it calls base class OnPaint method (line 35). Next it checks if custom appearance is enabled and if it is enabled continues. The code of OnPaint method is well commented and you can go through it. i hope there will not be any problem to understand the code. The class also overrides OnAutoSizeChanged method. Our implementation presets AutoSize to be false in the case if custom UI appearance is enabled. It is done in order to prevent standard auto sizing which will destroy our appearance.
public class CustomCheckBox : CheckBox
{
private CustomCheckBoxUIAppearance _customAppearance;
private bool _enableCustomUIAppearance;
[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 EnableCustomUIAppearance
{
get { return _enableCustomUIAppearance; }
set
{
if (_enableCustomUIAppearance != value)
{
_enableCustomUIAppearance = value;
if (AutoSize)
{
AutoSize = false;
}
Invalidate();
}
}
}
[Description("This property specifies the way of drawing the control."), Category("Custom Appearance"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public CustomCheckBoxUIAppearance CustomAppearance
{
get { return _customAppearance; }
}
public CustomCheckBox()
{
_enableCustomUIAppearance = true;
_customAppearance = new CustomCheckBoxUIAppearance(this);
}
protected override void OnPaint(PaintEventArgs pevent)
{
// Call base class' OnPaint
base.OnPaint(pevent);
if (!_enableCustomUIAppearance)
{
return;
}
// padding of the standard CheckBox
int offset = 2;
// distance betwen CechBox area and included label
int distance = 5;
// tick's part height placed above the border
int tickOffset = 6;
// both faces of check box square are 11 pixels
int checkBoxWidth = 11;
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 offsets
int leftOffset = offset + Padding.Left;
int topOffset = (int)(ClientRectangle.Height - stringMeasure.Height) / 2;
if (topOffset < 0)
{
topOffset = offset + Padding.Top;
}
else
{
topOffset += Padding.Top;
}
if (Checked)
{
// Fill CheckBox's rectangle
graphics.FillRectangle(new SolidBrush(_customAppearance.CheckedBackColor), leftOffset, topOffset + tickOffset, checkBoxWidth, checkBoxWidth);
// Draw Checkox's rectangle
graphics.DrawRectangle(new Pen(_customAppearance.CheckedBorderColor, _customAppearance.BorderThicness), leftOffset, topOffset + tickOffset, checkBoxWidth, checkBoxWidth);
// Draw tick
Point[] points = new Point[]
{
new Point(leftOffset, topOffset + tickOffset + _customAppearance.TickThickness),
new Point(leftOffset + (int)(checkBoxWidth / 2), topOffset + (checkBoxWidth - _customAppearance.TickThickness) + tickOffset),
new Point(leftOffset + checkBoxWidth + 1, topOffset)
};
graphics.DrawLines(new Pen(_customAppearance.TickColor, _customAppearance.TickThickness), points);
}
else
{
// Fill CheckBox's rectangle
graphics.FillRectangle(new SolidBrush(_customAppearance.BackColor), leftOffset, topOffset + tickOffset, checkBoxWidth, checkBoxWidth);
// Draw Checkox's rectangle
graphics.DrawRectangle(new Pen(_customAppearance.BorderColor, _customAppearance.BorderThicness), leftOffset, topOffset + tickOffset, checkBoxWidth, checkBoxWidth);
}
graphics.DrawString(Text, Font, new SolidBrush(ForeColor), new Point(leftOffset + checkBoxWidth + distance, topOffset + tickOffset));
}
protected override void OnAutoSizeChanged(EventArgs e)
{
if (_enableCustomUIAppearance)
{
if (AutoSize)
{
AutoSize = false;
}
}
base.OnAutoSizeChanged(e);
}
}
Here’s how CustomCheckBox controls appears when it is not checked and when it is checked. Unfortunetely, current implementation doesn’t support three check states but I leave it for your own implementation.

July 10th, 2008 at 5:37 pm
[...] Create custom UI appearance for WinForms CheckBox - part 2 [...]
July 11th, 2008 at 4:47 pm
[...] Create custom UI appearance for WinForms CheckBox - part 2 [...]
July 30th, 2008 at 4:40 pm
[...] Create custom UI appearance for WinForms CheckBox - part 2 [...]
July 31st, 2008 at 3:06 pm
[...] posts in this series: Post 1, Post 2, Post 3, Post 4, Post 5, Post 6, Source [...]
August 6th, 2008 at 4:36 am
thank you