Click here to Skip to main content
16,015,351 members
Articles / Desktop Programming / WPF
Article

Binding TextBlock, ListBox, RadioButtons to Enums

Rate me:
Please Sign up or sign in to vote.
4.73/5 (12 votes)
23 Nov 2010CPOL7 min read 71.9K   1.8K   26   8
Binding WPF controls to an enum property
ScreenShot_small.PNG

Introduction

This post demonstrates different ways to bind WPF controls to an enum property so that changing the selected item on the control automatically updates the value of the property.

Examples for a ListBox, ComboBox, a group of RadioButtons and a ListBox of RadioButtons is shown.

A way to convert the enum value to a user-friendly string is also shown.

Using the Code

Summary

You have an enum property such as the following:

C#
private enum State
{
    Virginia,
    WestVirginia,
    NorthCarolina,
    SouthCarolina
};

and a UserControl with the property such as the following:

C#
private States _state;
public  States State
{
    get { return _state; }
    set { _state = value; }
}

and you want to hook up a combo box, a list box or a panel of radio buttons to the property to allow the user to select a specific value.

1 - Create the User Control

We want to bind to the property in our UserControl, so we will have to change it fire the PropertyChanged event.

So we do the following:

  1. Add the System.ComponentModel namespace:
  2. C#
    using System.ComponentModel;
  3. Add the INotifyPropertyChanged interface to our UserControl and implement the interface by adding a PropertyChanged event:
  4. C#
    public partial class UserControl1 : UserControl, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
  5. Change the property that raises the PropertyChanged event when the value changes:
  6. C#
    private States _state;
    public States State
    {
        get { return _state; }
        set
        {
            if ( _state != value )
            {
                _state = value;
    
                if ( PropertyChanged != null )
                {
                     PropertyChanged( this, 
    		new PropertyChangedEventArgs( "State" ));
                }
            }
        }
    }
  7. Make it pretty.
  8. If you have multiple properties, this snippet of code takes a lot of coding. We can separate this out by creating a convenience function to check and call the PropertyChanged event.

    C#
    private void RaisePropertyChanged( string name )
    {
        if ( PropertyChanged != null )
        {
                PropertyChanged( this, new PropertyChangedEventArgs( name ));
        }
    }

    Even better, we can create a generic function to handle pretty much the whole thing:

    C#
    public void SetWithNotify<T>( string name, ref T currValue, T newValue ) 
    	where T : struct, IComparable
    {
        if ( currValue.CompareTo( newValue ) != 0 )
        {
            currValue = newValue;
            RaisePropertyChanged( name );
        }
    }

    Now the code for the property reads like this:

    C#
    private States _state;
    public States State
    {
        get { return _state; }
        set { SetWithNotify( "State", ref _state, value ); }
    }

2 - Set the DataContext for the UserControl

Bindings use the DataContext to locate the source to bind to. This is usually the UserControl (and is usually a reference to a ViewModel). Since this is a simple test program, I am going to break all the MVVM rules and set the DataContext for the UserControl to itself. This will, in effect, make the UserControl both the View and the ViewModel.

C#
public UserControl1()
{
    this.InitializeComponent();

    this.DataContext = this;
}

3 - Bind a TextBlock to the Enum Property

First we will add a TextBlock to show the selected value of the property.

Binding to the TextBlock is very simple, just add the binding to the State property. Since the control is display only, we only need a single one-way binding (the default for TextBlocks). The change to the property will raise the PropertyChanged event and the text block will be updated with the new property value.

XML
<TextBlock Text="{Binding State}" />

4 - Bind a ListBox to the Enum Property

When creating a ListBox, there are two properties in the ListBox that need to be set: ItemsSource and SelectedItem. The ItemsSource will be a list of enums. The SelectedItem will bind to our property.

There are many ways to create and bind the ItemsSource property on the ListBox. I will list a few.

  1. Create a property that returns a list of enums.
  2. One way is to create a property in the UserControl that returns the list of enums. Since this list will not change, it can be a read-only property.

    C#
    public List<States> ListOfStates
    {
        get
        {
            List<States> list = new List<States>();
    
            list.Add( States.NorthCarolina );
            list.Add( States.SoundCarolina );
            list.Add( States.Virginia );
            list.Add( States.WestVirginia );
    
            return list;
        }
    }

    Then the binding in the ListBox is:

    XML
    <ListBox  ItemsSource="{Binding ListOfStates}"
              SelectedItem="{Binding State, Mode=TwoWay}" />
  3. Get the list of values from the enum.
  4. The System.Enum class contains a static function GetValues which returns a list of all enum values. You need to pass the enum type as the parameter to the function.

    C#
    public System.Array ListOfStates
    {
        get
        {
            return Enum.GetValues( States.Virginia.GetType() );
        }
    }

    Note that when you do this, you do not have any control over the order.

  5. Create the list in the XAML using an ObjectDataProvider.
  6. In order to create a list of enums in XAML, we will create the list and put it into the Resources for the UserControl. Then we will bind to the list using the StaticResource binding call.

    First, we need to define the System.Enum type in XAML by adding the System namespace:

    XML
    xmlns:sys="clr-namespace:System;assembly=mscorlib"

    Then we can use the ObjectDataProvider class to call the GetValues function on the System.Enum type.

    XML
    <UserControl.Resources>
        <ObjectDataProvider MethodName="GetValues" 
    	ObjectType="{x:Type sys:Enum}" x:Key="States">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:States" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </UserControl.Resources>

    Now the binding looks like the following:

    XML
    <ListBox  ItemsSource="{Binding Source={StaticResource ListOfStates}}"
                  SelectedItem="{Binding State, Mode=TwoWay}" />
  7. Create the list in the XAML using an ValueConverter.
  8. Another way to create the list of enums is to use a ValueConverter. The converter will convert an object of type enum and return the list of values for the specified enum.

    C#
    [ValueConversion(typeof(System.Enum), typeof(System.Array))]
    public class EnumToValuesConverter : IValueConverter
    {
       public object Convert(object value, Type targetType, object parameter,
    		System.Globalization.CultureInfo culture)
       {
          return Enum.GetNames(value.GetType());
       }
    
       public object ConvertBack(object value, Type targetType, object parameter,
    		System.Globalization.CultureInfo culture)
       {
          throw new NotImplementedException();
       }
    }

    Now we put the converter into the UserControl resources and reference it in the binding. Note that we are binding both the ItemsSource and the SelectedItem to the State property.

    XML
    <UserControl.Resources>
        <local:EnumToValuesConverter x:Key="EnumToValuesConverter" />
    </UserControl.Resources>
    
    <ListBox
        ItemsSource="{Binding State, Converter={StaticResource EnumToValuesConverter}}"
        SelectedItem="{Binding Path=State, Mode=TwoWay}" />
  9. Create the list in the XAML using a Singleton ValueConverter.
  10. Make the converter a Singleton (implement the Singleton pattern) by adding the following 2 lines to the converter:

    C#
    private static EnumToValuesConverter _Instance = new EnumToValuesConverter();
    
    public static EnumToValuesConverter Instance { get { return _Instance; }}

    Once the converter is a singleton, remove it from the resources, and use the x:Static expression to get the instance:

    XML
    <ListBox
        ItemsSource="{Binding State, 
    	Converter={x:Static local:EnumToValuesConverter.Instance}}"
        SelectedItem="{Binding Path=State, Mode=TwoWay}" />

5 - Bind a Combo Box to the Enum Property

Binding to the combo box is the same as binding to the ListBox, using the ItemsSource and SelectedItem properties.

XML
<ComboBox
    ItemsSource="{Binding State, 
	Converter={x:Static local:EnumToValuesConverter.Instance}}"
    SelectedItem="{Binding State, Mode=TwoWay}" />

6 - Bind a Radio Box Group to the Enum Value

If you want a group box with a stack of radio buttons, one for each enum value, one way to do this would be to create one RadioButton for each enum value and then bind the IsChecked property for the RadioButton to the enum value using a converter which returns True if the enum associated with the radio button matches the property value and False if it is different than the property value.

Binding Radio Box Group using an Enum To Boolean Converter

First, we need a converter which uses the ConverterParameter property to specify the enum value. The converter will return true if the binding property matches the ConverterParameter and false if it does not.

C#
[ValueConversion( typeof( System.Enum ), typeof( bool ) )]
public class EnumToBooleanConverter : IValueConverter
{
    public object Convert( object value, Type targetType, object parameter,
		System.Globalization.CultureInfo culture )
    {
        return value.Equals( parameter );
    }

    public object ConvertBack( object value, Type targetType, object parameter,
		System.Globalization.CultureInfo culture )
    {
        if ( value.Equals( false ) )
            return DependencyProperty.UnsetValue;
        else
            return parameter;
    }
}

Once we have the EnumToBooleanConveter, we create an instance of it in the UserControl resources and bind the IsChecked property to the State property. Also, the Content for the Radio Button is also set using a binding.

XML
<RadioButton
    Content="{x:Static local:States.Virginia}"
    IsChecked="{Binding Path=State,
        Converter={StaticResource EnumToBooleanConverter},
            ConverterParameter={x:Static local:States.Virginia}}" />

Note that you will have to create a RadioButton for each enum value.

7 - Bind a ListBox of RadioButtons to an Enum Property

Of course, you do not want to create a Radio button for each enum value. You want WPF to automatically do this. The way to handle this is to create a ListBox and set the ItemsSource for the listbox to the enum (using our EnumToValuesConveter) and binding the SelectedItem of the ListBox to the State property.

XML
<ListBox
    ItemsSource="{Binding State, 
	Converter={x:Static local:EnumToValuesConverter.Instance}}"
    SelectedItem="{Binding State, Mode=TwoWay}" />

Once this is done, we create a template for the ListBoxItem, specifying a RadioButton in the ControlTemplate. In the template, we bind the IsChecked property of the RadioButton to the IsSelected property of the ListBoxItem (mode is two-way).

XML
<ListBox.Resources>
    <Style TargetType="{x:Type ListBoxItem}" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <RadioButton
                        Content="{TemplateBinding ContentPresenter.Content}"
                        IsChecked="{Binding Path=IsSelected,
                            RelativeSource={RelativeSource TemplatedParent},
                            Mode=TwoWay}" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ListBox.Resources>

This will automatically create a RadioButton for each ListBoxItem.

8 - Use a Different Converter to Change the Display Value for the Enum

In each of the cases listed above, the list of values displayed to the user was the value of the enum (as displayed by ToString()). It is usually not the case that the enum value is the value that we want to show to the user. For example, we would want to display "West Virginia" (with a space), not "WestVirginia" (no space).

In order to display this user-friendly version, we need converter, one that converts from an enum value to a string value. For example, in our case, we can create an EnumToSpacedStringConverter, which just inserts a space before each capitalized character (except the first one). In addition, we will need another converter to create the list of strings. Thus, the combo box, list box, radio buttons, are displaying string values as supplied from our converters.

First we write a quick InsertSpaces function:

C#
private string InsertSpaces( string inString )
{
   string outString = "";

   foreach( char x in inString )
   {
       if ( outString.Length > 0 && x >= 'A' && x <= 'Z' )
       {
           outString += ' ';
       }

       outString += x;
   }

    return outString;
}

We then create the converter that creates the list of spaced strings (again this only goes one way).

C#
public object Convert(object value, Type targetType, object parameter,
	System.Globalization.CultureInfo culture)
 {
    Array array = Enum.GetValues(value.GetType());

    List<string> strings = new List<string>();

    foreach( object o in array )
    {
        strings.Add( InsertSpaces( o.ToString() ));
    }

    return strings;
}

public object ConvertBack(object value, Type targetType, object parameter,
	System.Globalization.CultureInfo culture)
{
    throw new NotImplementedException();
}

Finally, we create the converter to handle the spaced strings. The hard part here is the ConvertBack routine, which converts the supplied string to the enum value.

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

public object ConvertBack(object value, Type targetType, object parameter,
   System.Globalization.CultureInfo culture)
{
   Array array = Enum.GetValues(targetType);

   foreach( object o in array )
   {
       if ( InsertSpaces( o.ToString() ) == (string) value )
       {
           return o as System.Enum;
       }
   }

   return DependencyProperty.UnsetValue;
}

Once our converters are written, we add them to the resources section.

XML
<UserControl.Resources>
    <local:EnumToSpacedStringConverter x:Key="SpacedStringConverter" />
    <local:EnumToSpacedStringsConverter x:Key="SpacedStringsConverter" />

The text block becomes:

XML
<TextBlock  Text="{Binding State, Converter={StaticResource SpacedStringConverter}}" />

The ListBox and ComboBox become:

XML
<ListBox
    ItemsSource="{Binding State, Converter={StaticResource SpacedStringsConverter}}"
    SelectedItem="{Binding State, Mode=TwoWay, 
		Converter={StaticResource SpacedStringConverter}}" />

Each RadioButton in the group becomes (and this is a little tricky):

XML
<RadioButton
    Content="{Binding
              Source={x:Static local:States.NorthCarolina},
              Converter={StaticResource SpacedStringConverter}}"
    IsChecked="{Binding Path=State,
                Converter={StaticResource EnumToBooleanConverter},
                ConverterParameter={x:Static local:States.NorthCarolina}}"/>

And the RadioButton ListBox becomes:

XML
<ListBox  Background="{x:Null}"
          ItemsSource="{Binding State, Converter={StaticResource SpacedStringsConverter}}"
          SelectedItem="{Binding Path=State, Mode=TwoWay, 
		Converter={StaticResource SpacedStringConverter}}">
      <ListBox.Resources>
          <Style TargetType="{x:Type ListBoxItem}">
              <Setter Property="Template">
                  <Setter.Value>
                       <ControlTemplate>
                           <RadioButton
                                Content="{TemplateBinding ContentPresenter.Content}"
                                IsChecked="{Binding Path=IsSelected, 
				RelativeSource={RelativeSource TemplatedParent},
				Mode=TwoWay}" />
                      </ControlTemplate>
                  </Setter.Value>
               </Setter>
            </Style>
        </ListBox.Resources
</ListBox>

Points of Interest

The program works correctly when the user uses the mouse to select the items. However keyboard to tab between the controls causes the program to behave incorrectly. I do not know the reason for this.

History

  • 11/21/2010 - Initial release

License

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


Written By
Chief Technology Officer Crayne Scientific, Inc
United States United States
General philosophy:
Make it Work
Make it Fast
Make it Pretty
Make it Indestructible

Comments and Discussions

 
QuestionHow to extract the output information or the selection result of the radio button from code-behind? Pin
Member 995312131-Jul-13 20:21
Member 995312131-Jul-13 20:21 
QuestionA couple of notes Pin
hansen.c1-Jul-12 12:10
hansen.c1-Jul-12 12:10 
GeneralSome thoughts Pin
DaProgramma29-Nov-10 2:31
DaProgramma29-Nov-10 2:31 
GeneralRe: Some thoughts Pin
Craig Wooldridge29-Nov-10 6:06
professionalCraig Wooldridge29-Nov-10 6:06 
GeneralRe: Some thoughts Pin
DaProgramma29-Nov-10 6:58
DaProgramma29-Nov-10 6:58 
>When storing an enum in the database (or xml file), I store either the enum value or the name (i.e. ToString())
>Storing the value allows me to change the name of the enum and not break the program.

What? You have: enum { all, some, one, none }

1: Later in the year, your sales people now want the combo entries to read (All, Some, One, None).
Even in a single language program you are in deep trouble, believe me! If you change the enum in your program to reflect that - are U REALLY sure that noone uses the enum-strings for some comparison, in a database, in some XML text, etc? Do you REALLY want to read and understand the whole program inclusive all SQL queries, XML-files etc. to make sure you changed all "one" to "One"? Definitely not me, and not at 05:00 in the morning when some last minute fixes are going to be made for delivery tis day...
Resume: NEVER use names of variables outside their menaingful scope.

2: Even later, someone requests the combo to be ( all, some, two, one, none ). If you EVER used the values of the enum for any purpose, you are in even deeper trouble. A value of 4 coming from an old database was used to mean "never" but now it means "one". BTW, I saw more than one program with this kind of error.

You can do enum { all, some, one, none, two } - but your combo looks ugly then, and I'll guarantee some apprentice some time in the future will get it wrong.
You can do enum { all=0, some=1, two=4, one=2, none=3 )- but then your program looks ugly.
Resume: NEVER use values of enums outside their menaingful scope.
GeneralRe: Some thoughts Pin
Craig Wooldridge29-Nov-10 12:37
professionalCraig Wooldridge29-Nov-10 12:37 
GeneralGood article Pin
louislong23-Nov-10 4:15
louislong23-Nov-10 4:15 
GeneralRe: Good article Pin
Craig Wooldridge23-Nov-10 4:20
professionalCraig Wooldridge23-Nov-10 4:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.