Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Binding RadioButtons to an Enum in Silverlight

5.00/5 (30 votes)
5 Sep 2011CPOL7 min read 106K   1.3K  
How to bind an enum to radiobuttons in Silverlight/WPF

Introduction

Earlier today, I was working on a new form for my Silverlight application, and I realized that I needed to bind an enum to a group of RadioButtons. Being a highly skilled, advanced software engineer, I rolled up my sleeves, put on my thinking hat, and.... attempted to Google the solution. I knew what the basic solution should look like in that I was sure a creative converter solution would be involved, but I was looking to save some time and effort. What I found online however, was very disappointing. I wasn't the first person to face this situation and ask about it, but the messages I read on the forums all seemed to follow a common theme... "It can't be done". Everyone recommended that you simply handle the situation by using the code-behind and manually massaging the state of the controls as required. I don't know about you, but I know the sweet smell of bull droppings when I smell them, and this advice just reeked. So once again, I rolled up the old sleeves, this time in anticipation of doing some actual work, and decided to fix this problem for all future developers seeking a quick-fix to the situation.

Background

I just want to take a quick moment to overstate the obvious for those new to Silverlight. If you come from an ASP.NET or other background, chances are you've spent years managing the state of your UI controls in code-behind. And if that's the case, then at some point in time, you've most likely learned a hard lesson about how failure-prone this approach can be. Sure, it works great in a perfect world made of bug-free code, but in developer-land, things rarely go this smoothly. And, when object values change unexpectedly, your control may not reflect the correct value. This is an annoyance under any circumstance, but in a critical medical or financial application, the results can be catastrophic. Having controls bound to data is the only way to insure that your UI is accurately representing the data it is attached to, and is rarely subject to bugs (not 100%, but a well written converter is usually a small, very test-able portion of code, as you will see).

Using the Code

Every good demo requires a made-up company example (at least, according to Microsoft it does), so here we go. Our client is a company called SmallMediumLarge.com, or SML.com in shorthand. SML.com sells t-shirts. Their "hook" is that they sell only one kind of t-shirt every day, and the only choice you have in the matter is to choose the shirt size, which is limited to small, medium, or large. As a contractor for SML.com, you've been asked to code the size page. As a start-up, they don't have any money to pay you yet, and have offered you a free t-shirt as compensation (in small, medium, or large only). They don't expect much from you, they just want a page with three radio buttons representing the various shirt sizes, and they want those buttons bound to an enum in their Shirt object. (If you've been developing as long as I have, then sadly you realize just how "not" an unrealistic scenario this is).

Fortunately for you, the last contractor to earn a free t-shirt had been asked to code the Shirt object, and had a little experience with Silverlight. He was smart enough to create a simple enum representing the shirt sizes, and added that to a Shirt class with the enum exposed. Better yet, he knew what a PropertyChanged notification was and how to use it, and made sure that whenever the shirt size was set, the Shirt object would send the proper notifications to any control bound to it. Thanks Mr. other contractor guy, you earned your free t-shirt buddy. His code looks like this:

C#
public class Shirt : INotifyPropertyChanged
{
    private Sizes _size;

    public Shirt()
    {
        Size = Sizes.Small;
    }

    public Sizes Size
    {
        get { return _size; }
        set
        {
             _size = value;
             RaisePropertyChanged("Size");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
     }
}

public enum Sizes
{
    Small,
    Medium,
    Large
}

Let's begin this project the way all good projects are begun - with a bottle of Diet Mountain Dew and Ozzy Osbourne playing on the iPod. Oh, firing up Visual Studio might help too. I'm using VS 2010 and Silverlight 4, but you can do whatever you want to. Add a Label or TextBlock to describe what it is we want from our customer, three radio buttons to represent our three size enums, and a TextBox which we'll use to help monitor the value of the bound Shirt object. I'll post my own award winning design here in order to save you some time...

XAML
<UserControl x:Class="DemoBoundRadioButtons.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:DemoBoundRadioButtons="clr-namespace:DemoBoundRadioButtons"
    mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.Resources>
        <DemoBoundRadioButtons:RadioButtonConverter x:Key="RadioButtonConverter" />
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White" Margin="30">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Text="Choose a t-shirt size:" 
               Margin="0 0 0 10" HorizontalAlignment="Center" />
        <StackPanel Orientation="Horizontal" 
                 Grid.Row="1" HorizontalAlignment="Center">
            <RadioButton Content="Small" Margin="0 0 10 0" 
                 GroupName="Sizes" IsChecked="True" />
            <RadioButton Content="Medium" 
                 Margin="0 0 10 0" GroupName="Sizes" />
            <RadioButton Content="Large" 
                 Margin="0 0 10 0" GroupName="Sizes" />
         </StackPanel>
         <TextBox Text="{Binding Size, Mode=TwoWay}" 
           Grid.Row="2" Margin="0 10 0 0" 
           Width="100" HorizontalAlignment="Center" 
           TextAlignment="Center" />
    </Grid>
</UserControl>

Functionally speaking, the only thing to note here is that the TextBox has been bound to the Size property of the Shirt object, so it can serve as a clear indicator of the currently selected enum. (Marking it as Mode=TwoWay wasn't required, but we'll get to that later). Also, the RadioButtons have all been added to a Group so that when one is selected, the others are de-selected. I bound a new Shirt object to the DataContext of the entire page like this:

C#
public MainPage()
{
    InitializeComponent();
    Loaded += MainPage_Loaded;
}

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    DataContext = new Shirt();
}

Now let's get to those CheckBoxes. The first thing we need to do is wire up a converter. I just added it to the code-behind file, but you can make it a separate file if you so choose. My base code looks like this:

C#
public class RadioButtonConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
           object parameter, CultureInfo culture)
    {
       
    }

    public object ConvertBack(object value, Type targetType, 
           object parameter, CultureInfo culture)
    {
      
    }
}

And don't forget to add a reference to the converter as a Resource:

XAML
<UserControl.Resources>
    <DemoBoundRadioButtons:RadioButtonConverter x:Key="RadioButtonConverter" />
</UserControl.Resources>

Alright, now that we are done with all the basic framework, let's fill in the details. The real challenge here is that a RadioButton is only capable of boolean values. True or false. On or off. In some cases, third party controls will allow a third "partial" state, but for the purposes of this discussion, let's assume we are working with a standard RadioButton. How exactly do we convert a boolean value into an enumerated value? Well, the answer lies in the fact that the converter signature allows us to pass it some additional information, aside from the value of the RadioButton. And that is our light at the end of the tunnel. Consider the following code, then let's break it down.

XAML
<RadioButton Content="Small" Margin="0 0 10 0" 
  GroupName="Sizes" 
  IsChecked="{Binding Size, Mode=TwoWay, ConverterParameter=Small, 
             Converter={StaticResource RadioButtonConverter}}" />
<RadioButton Content="Medium" Margin="0 0 10 0" 
  GroupName="Sizes" 
  IsChecked="{Binding Size, Mode=TwoWay, ConverterParameter=Medium, 
             Converter={StaticResource RadioButtonConverter}}" />
<RadioButton Content="Large" Margin="0 0 10 0" 
  GroupName="Sizes" 
  IsChecked="{Binding Size, Mode=TwoWay, ConverterParameter=Large, 
             Converter={StaticResource RadioButtonConverter}}" />

The IsChecked value of each RadioButton is bound to the Shirt.Size value, but it also contains another parameter - the ConverterParameter. The ConverterParameter gives us the power to pass some additional information to the converter; in this case, it identifies which RadioButton represents which Shirt.Size we want to order. Couple that with the fact we have the RadioButtons in a group so that only one RadioButton will be selected at a time, and now we can positively identify which Shirt.Size is being selected. The one we want has IsChecked == true, and the ConverterParameter tells us which Shirt.Size we want. So now, we just need to code the Converter itself.

C#
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    return (value.ToString() == parameter.ToString());
}

public object ConvertBack(object value, Type targetType, 
       object parameter, CultureInfo culture)
{
    return (bool) value ? Enum.Parse(targetType, parameter.ToString(), true) : null;
}

When a RadioButton is clicked, the ConvertBack() function is called. Actually, in this case, it is called three times, because the values of all the RadioButtons in the group are evaluated. If the RadioButton being evaluated is not checked (IsChecked == false), then null is returned, and basically, nothing happens. However, if the value evaluates to true, then the magic happens. The ConverterParameter comes into play now, and we use Enum.Parse to evaluate each Size in Sizes, and when we find the one that matches our parameter, we have a winner, and that enum value is returned. Easy-peasy.

For the most part, we are done. But I hate to do anything half way, so let's finish it. Remember that I said we wired up the TextBox as Mode=TwoWay? Well, let's bring that into play. Run the example, select the Small RadioButton, and then type "Medium" into the TextBox. Make sure to press the Tab key when you are done so that it "takes". Look at that! The Medium RadioButton is now selected! I love converters. Let's look at what happened.

When you typed "Medium" into the TextBox, every RadioButton that is bound to the same Shirt.Size value is evaluated (so again, three times in this example). The math is simple - if the string that you entered into the TextBox matches the ConverterParameter of the RadioButton being evaluated, then the true value is returned and the RadioButton is selected. Even easier-peasier.

Wrap Up

I hope you had as much fun reading this article as I did writing it. If there is a moral to this story, it is to never take "it can't be done" as an answer. Sometimes, it just means no one did it "yet". Figuring this one out was certainly no great feat. No brain cells were harmed in the writing of this article. If you found this article of value to you, then please pay it forward by writing about your solution to a problem in the future.

History

  • 18th May, 2010: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)