~/src/www.mokhan.ca/xlgmokha [main]
cat those-darn-checkboxes.md
those-darn-checkboxes.md 23498 bytes | 2007-09-14 00:00
symlink: /opt/dotnet/those-darn-checkboxes.md

Those Darn Checkboxes

Tonight I spent sometime playing with checkboxes! (Yes I know, I should get out more…) Here’s what I learned. Let’s say you’ve got a form with a group of checkboxes. That might look something like this:

Yes I know this is one of the most impressive forms you’ve ever seen. But let’s say that you’ve got a group of checkboxes and you need to capture which checkboxes are selected. You could wire up an event handler for the “Save” button’s click event, then write up a bunch of if-else statements to see which checkboxes are checked and yada yada…

Your code might look something like…

if ( checkBox1.Checked ) {}
if ( checkBox2.Checked ) {}
if ( checkBox3.Checked ) {}

I didn’t like this so I wanted to come up with a different way, something that’s not so smelly. Here’s what I came up with. The ICheckBoxGroup where T is a full blown type that represents something in the real world.

internal interface ICheckBoxGroup< T > {
  void Add( CheckBox checkbox, T item );
  IEnumerable< T > GetAllEnabled( );
  IEnumerable< T > GetAllThatSatisfies( Predicate< CheckBox > condition );
}

This allows me to bind a full blown domain object to each checkbox. In this example I’m binding a Sport to each checkbox.

  ICheckBoxGroup< ISport > group = new CheckBoxGroup< ISport >( );
  group.Add( checkBox1, Sport.Hockey );
  group.Add( checkBox2, Sport.BasketBall );
  group.Add( checkBox3, Sport.FootBall );
  group.Add( checkBox4, Sport.Golf );
  group.Add( checkBox5, Sport.Tennis );

When the “Save” button is clicked I’m asking for all the Sports that have been selected like this…

  StringBuilder builder = new StringBuilder( );
  foreach ( ISport employee in group.GetAllEnabled( ) ) {
      builder.AppendFormat( "{0}{1}", employee, Environment.NewLine );
  }
  MessageBox.Show( builder.ToString( ) );

The “GetAllEnabled” method is returning an IEnumerable. This means there's no conversion from if checkbox1.checked == true then Hockey was selected. You're just handed back the type that the checkbox represents, no translation required. The implementation of CheckBoxGroup looks like this:

  public class CheckBoxGroup< T > : ICheckBoxGroup< T > {
      public CheckBoxGroup( ) {
          _checkboxes = new List< CheckBox >( );
      }

      public void Add( CheckBox checkbox, T item ) {
          checkbox.Tag = item;
          checkbox.Text = item.ToString( );
          _checkboxes.Add( checkbox );
      }

      public IEnumerable< T > GetAllEnabled( ) {
          return GetAllThatSatisfies( delegate( CheckBox box ) { return box.Checked; } );
      }

      public IEnumerable< T > GetAllThatSatisfies( Predicate< CheckBox > condition ) {
          foreach ( CheckBox checkBox in _checkboxes ) {
              if ( condition( checkBox ) ) {
                  yield return ( T )checkBox.Tag;
              }
          }
      }

      private readonly IList< CheckBox > _checkboxes;
  }

Just remember to override ToString on your types otherwise the default implementation will just write the fully qualified name of your type as the text next to your checkbox. I know this example could be greatly improved, but after a bus ride home pondering about this, this was what I could come up. Feel free to toss me your example.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace PlayingWithWinForms {
	public partial class Form1 : Form {
		public Form1( ) {
			InitializeComponent( );

			ICheckBoxGroup< ISport > group = new CheckBoxGroup< ISport >( );
			group.Add( checkBox1, Sport.Hockey );
			group.Add( checkBox2, Sport.BasketBall );
			group.Add( checkBox3, Sport.FootBall );
			group.Add( checkBox4, Sport.Golf );
			group.Add( checkBox5, Sport.Tennis );

			button1.Click += delegate {
			                 	StringBuilder builder = new StringBuilder( );
			                 	foreach ( ISport employee in group.GetAllEnabled( ) ) {
			                 		builder.AppendFormat( "{0}{1}", employee, Environment.NewLine );
			                 	}
			                 	MessageBox.Show( builder.ToString( ) );
			                 };
		}
	}

	internal interface ICheckBoxGroup< T > {
		void Add( CheckBox checkbox, T item );
		IEnumerable< T > GetAllEnabled( );
		IEnumerable< T > GetAllThatSatisfies( Predicate< CheckBox > condition );
	}

	public class CheckBoxGroup< T > : ICheckBoxGroup< T > {
		public CheckBoxGroup( ) {
			_checkboxes = new List< CheckBox >( );
		}

		public void Add( CheckBox checkbox, T item ) {
			checkbox.Tag = item;
			checkbox.Text = item.ToString( );
			_checkboxes.Add( checkbox );
		}

		public IEnumerable< T > GetAllEnabled( ) {
			return GetAllThatSatisfies( delegate( CheckBox box ) { return box.Checked; } );
		}

		public IEnumerable< T > GetAllThatSatisfies( Predicate< CheckBox > condition ) {
			foreach ( CheckBox checkBox in _checkboxes ) {
				if ( condition( checkBox ) ) {
					yield return ( T )checkBox.Tag;
				}
			}
		}

		private readonly IList< CheckBox > _checkboxes;
	}

	public class Sport : ISport {
		private Sport( string name ) {
			_name = name;
		}

		public static readonly ISport Hockey = new Sport( "Hockey" );
		public static readonly ISport BasketBall = new Sport( "Basket Ball" );
		public static readonly ISport FootBall = new Sport( "Football" );
		public static readonly ISport Golf = new Sport( "Golf" );
		public static readonly ISport Tennis = new Sport( "Tennis" );

		public string Name {
			get { return _name; }
		}

		public override string ToString( ) {
			return Name;
		}

		private readonly string _name;
	}

	public interface ISport {
		string Name { get; }
	}
}