Home > Intermediate > The Three-State CheckBox

The Three-State CheckBox

In my previous post I explained why you should use the ‘CustomControl (WPF)’ template from the Add New Item dialog when you want to create a custom control. But of course there’s an exception to this rule! When you only want to make (minor) changes to the functionality of a base control, you don’t always need the stuff that Visual Studio generates for you. That stuff is generated so you can create your own presentation (ControlTemplate) for your control. But what if you only want to change the logic?

Consider the following scenario:

Some people want to use the CheckBox’s ‘Indeterminate’ state as a representation for ‘the user hasn’t made a choice yet’. And if you’re like me, if I click on a checkbox I expect it to go into its Checked state. And if you combine these facts you might find it annoying that when you click on an Indeterminate checkbox, it turns Unchecked, instead of Checked.

Now, can we fix this without all the stuff that Visual Studio generates for you? After all, we don’t want to change the presentation of the checkbox or its states, just the sequence order of the states. The answer is: yes, of course we can!

In this case we don’t use the ‘CustomControl’ template, but we just add a new Class to our project. Taking this road, we don’t have the automatically generated static constructor with the DefaultStyleKey override in our class, but that’s fine, because in our scenario we explicitly don’t want an override to tell WPF to use a custom control template. We just want the standard control template to be used.

Now we only have to make our class inherit from the standard CheckBox and implement our modification:

public class CustomThreeStateCheckBox : CheckBox
    public CustomThreeStateCheckBox()
        // Always start in the 'Indeterminate' state
        base.IsChecked = null;

    protected override void OnToggle()
        // Change the sequence from: Unchecked - Checked - Indeterminate
        // to: Indeterminate - Checked - Unchecked
        if (this.IsChecked == false)
            this.IsChecked = this.IsThreeState ? null : ((bool?)true);
            this.IsChecked = new bool?(!this.IsChecked.HasValue);

Another example of a ‘semi-custom’ control could be a numeric TextBox. If you just want to ignore any character that cannot be parsed to a double, this simple class might be all you need:

public class NumericTextBox : TextBox
    protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
        // Ignore any character that fails to parse to a double
        double value;
        if (!Double.TryParse(e.Text, out value))
            e.Handled = true;

The advantage of these kinds of custom controls is that it’s less work and they will respect the Windows theme your application is running in, without having to create several different theme-styles for them.

So, every time you’re about to create a custom control, take the time to check if you really need a new control template, because it’s time well spent!

Download source code.

  1. Mathew
    November 5, 2009 at 10:49 pm

    Not about this post… but just a comment for you…
    Your post here http://social.msdn.microsoft.com/Forums/en/wpf/thread/53159ee5-e82b-4b51-a6cd-e698887c5cc6 saved me… I couldn’t figure out why my virtualization of an itemcontrol was not working. I really thank you for sharing your knowledge of WPF. I greatly appreciate it.

    Thanks again.

    • November 6, 2009 at 9:25 am

      Hi Matt, thanks for letting me know! And also for checking out my blog 🙂

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: