A New Skin for Ye Olde GroupBox






4.40/5 (4 votes)
Creating a custom GroupBox in WinForms that supports skinning
Version 2 is an improved version using a transparent panel.
Introduction
When applying a dark skin theme to my Windows Forms application which used a GroupBox
, everything seemed fine, see screendump below:
But as soon as I wanted to set the GroupBox.Enabled
property to false
, things did not look that good anymore.
As can be seen in the screendump, the standard GroupBox
on the right automatically chooses a black color for the disabled state:
Background
SkinSettings Class
The skin themes are in a separate class SkinSettings
, to keep the example small, it only has a few settings.
It also has a ColorDisabled()
method for calculating the custom disabled Color
.
using System;
using System.Drawing;
namespace CustomControls1
{
/// <summary>
/// A simple SkinSettings class.
/// This could be extended to support more control colors.
/// </summary>
public static class SkinSettings
{
public static Color FormBackColor { get; set; }
public static Color FormForeColor { get; set; }
public static Color FontColor { get; set; }
/// <summary>
/// Initialize, blue theme is default.
/// </summary>
static SkinSettings()
{
SetBlueTheme();
}
public static void SetBlueTheme()
{
FormBackColor = Color.FromArgb(50, 100, 150);
FormForeColor = Color.DarkBlue;
FontColor = Color.LightGray;
}
public static void SetGrayTheme()
{
FormBackColor = Color.FromArgb(200, 200, 200);
FormForeColor = Color.Black;
FontColor = Color.Black;
}
/// <summary>
/// Calculate the disabled color.
/// </summary>
/// <param name="color">The color.</param>
/// <returns>The disabled (lighter) color.</returns>
public static Color ColorDisabled(Color color)
{
int red = Math.Min(255, color.R + 40);
int green = Math.Min(255, color.G + 40);
int blue = Math.Min(255, color.B + 40);
return Color.FromArgb(red, green, blue);
}
}
}
CustomGroupBox Class
The CustomGroupBox
class is in fact an extended version of the standard GroupBox
control, note that you need to build the project first before it will appear in the Toolbox. It can be used just like a normal GroupBox
.
It handles RadioButtons
and CheckBoxes
events itself using the SetEventHandlers()
method which is called at runtime when the CustomGroupBox
is selected.
It also has its own Enabled
property which overrides the default Enabled
behaviour (using the new
keyword) which causes the ugly black colors.
namespace CustomControls1
{
using System;
using System.Drawing;
using System.Windows.Forms;
/// <summary>
/// Custom group box which supports SkinSettings.
/// Created as a normal class which inherits from GroupBox.
/// <see cref="SetEventHandlers"/> is called to handle RadioButtons and CheckBoxes correctly.
/// </summary>
public class CustomGroupBox : GroupBox
{
/// <summary>
/// Value indicating whether the GroupBox is enabled.
/// </summary>
private bool enabled = true;
private bool setEventHandlers;
public CustomGroupBox()
{
this.ForeColor = SkinSettings.FormForeColor;
}
/// <summary>
/// Gets or sets a value indicating whether the GroupBox is enabled.
/// Overrides the default property and draws controls in <see cref="ColorDisabled"/> color.
/// </summary>
public new bool Enabled
{
get
{
return this.enabled;
}
set
{
this.enabled = value;
this.UpdateControls(value);
}
}
/// <summary>
/// Set controls to disabled or enabled color.
/// </summary>
/// <param name="enabledLocal">True or false.</param>
private void UpdateControls(bool enabledLocal)
{
if (enabledLocal)
{
// Set to default Color.
this.ForeColor = SkinSettings.FontColor;
}
else
{
this.ForeColor = SkinSettings.ColorDisabled(this.BackColor);
}
foreach (var control in this.Controls)
{
if (control is TextBox)
{
var txt = control as TextBox;
txt.Enabled = enabledLocal;
}
if (control is Button)
{
var btn = control as Button;
btn.Enabled = enabledLocal;
}
}
}
/// <summary>
/// Custom CheckBox handler.
/// Only when GroupBox is enabled: check CheckBox.
/// </summary>
/// <param name="sender">The CheckBox object.</param>
/// <param name="e">The Event Args.</param>
private void CheckBoxClick(object sender, EventArgs e)
{
var cb = sender as CheckBox;
if (this.enabled && cb != null)
{
cb.Checked = !cb.Checked;
}
}
/// <summary>
/// Radio button click, when GroupBox enabled: check only one of the radiobuttons.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The event args.</param>
private void RadioButtonClick(object sender, EventArgs e)
{
var rb = sender as RadioButton;
if (this.enabled && rb != null)
{
foreach (var control in rb.Parent.Controls)
{
// Reset all RadioButtons.
if (control is RadioButton)
{
var rb2 = control as RadioButton;
rb2.Checked = false;
}
}
rb.Checked = true;
}
}
/// <summary>
/// On GroupBox activated set the event handlers.
/// </summary>
protected override void OnEnter(EventArgs e)
{
if (!setEventHandlers)
{
this.SetEventHandlers();
}
base.OnEnter(e);
}
/// <summary>
/// Set event handlers for RadioButtons and CheckBoxes in GroupBox.
/// Also sets AutoCheck property to false.
/// It is important to set the RadioButton.AutoCheck property to false, as we
/// are handling the Click event in a custom way.
/// </summary>
private void SetEventHandlers()
{
this.setEventHandlers = true;
foreach (var control in this.Controls)
{
if (control is CheckBox)
{
var chk = control as CheckBox;
chk.AutoCheck = false;
chk.Click += new System.EventHandler(this.CheckBoxClick);
}
if (control is RadioButton)
{
var rb = control as RadioButton;
rb.AutoCheck = false;
rb.Click += new System.EventHandler(this.RadioButtonClick);
}
}
}
}
}
The Form
The main form has a CustomGroupBox
and a standard GroupBox
. Nothing shocking here as most things are handled by the other classes:
namespace CustomControls1
{
using System;
using System.Windows.Forms;
public partial class Form1 : Form
{
/// <summary>
/// Use SkinSettings to set the Form colors.
/// </summary>
public Form1()
{
this.InitializeComponent();
this.SetColors();
}
/// <summary>
/// Enable / disable button.
/// </summary>
private void button1_Click(object sender, EventArgs e)
{
this.groupBox2.Enabled = !this.groupBox2.Enabled;
this.customGroupBox1.Enabled = !this.customGroupBox1.Enabled;
if (this.groupBox2.Enabled)
{
this.Text = "Custom groupbox enabled";
}
else
{
this.Text = "Custom groupbox disabled";
}
}
/// <summary>
/// Blue theme.
/// </summary>
private void button2_Click(object sender, EventArgs e)
{
SkinSettings.SetBlueTheme();
this.SetColors();
}
/// <summary>
/// Gray theme.
/// </summary>
private void button3_Click(object sender, EventArgs e)
{
SkinSettings.SetGrayTheme();
this.SetColors();
}
/// <summary>
/// Set the Form and control colors to SkinSettings.
/// Note that controls normally inherit the Form colors.
/// </summary>
private void SetColors()
{
this.BackColor = SkinSettings.FormBackColor;
this.ForeColor = SkinSettings.FormForeColor; // Also sets Button ForeColor.
this.groupBox2.ForeColor = SkinSettings.FontColor;
this.customGroupBox1.ForeColor = SkinSettings.FontColor;
}
}
}
Using the Code
I tested the application with VS2013, .NET 4.5 and Windows 7, but I'm pretty sure it will work with older versions too.
Points of Interest
For a larger scale application, the SkinSettings
class needs to be extended to support more Control types like Panel
, TextBox
, ToolStrip
, etc.
I also implemented a method that can apply the SkinSettings
to a whole Form by setting the colors in a loop that goes through all Controls in the Forms Control collection. To keep the example readable, this method was omitted.
History
- 13 June 2015: First version published
- 14 June 2015: Improved version using transparent panel