Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A New Skin for Ye Olde GroupBox

0.00/5 (No votes)
13 Jun 2015 1  
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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here