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:
private enum State
{
Virginia,
WestVirginia,
NorthCarolina,
SouthCarolina
};
and a UserControl
with the property such as the following:
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:
- Add the
System.ComponentModel
namespace:
using System.ComponentModel;
- Add the
INotifyPropertyChanged
interface to our UserControl
and implement the interface by adding a PropertyChanged
event:
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
- Change the property that raises the
PropertyChanged
event when the value changes:
private States _state;
public States State
{
get { return _state; }
set
{
if ( _state != value )
{
_state = value;
if ( PropertyChanged != null )
{
PropertyChanged( this,
new PropertyChangedEventArgs( "State" ));
}
}
}
}
- Make it pretty.
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.
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:
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:
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
.
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 TextBlock
s). The change to the property will raise the PropertyChanged
event and the text block will be updated with the new property value.
<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 enum
s. 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.
- Create a property that returns a list of
enum
s. One way is to create a property in the UserControl
that returns the list of enum
s. Since this list will not change, it can be a read-only property.
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:
<ListBox ItemsSource="{Binding ListOfStates}"
SelectedItem="{Binding State, Mode=TwoWay}" />
- Get the list of values from the
enum
. 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.
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.
- Create the list in the XAML using an
ObjectDataProvider
. In order to create a list of enum
s 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:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Then we can use the ObjectDataProvider
class to call the GetValues
function on the System.Enum
type.
<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:
<ListBox ItemsSource="{Binding Source={StaticResource ListOfStates}}"
SelectedItem="{Binding State, Mode=TwoWay}" />
- Create the list in the XAML using an
ValueConverter
. Another way to create the list of enum
s is to use a ValueConverter
. The converter will convert an object of type enum
and return the list of values for the specified enum
.
[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.
<UserControl.Resources>
<local:EnumToValuesConverter x:Key="EnumToValuesConverter" />
</UserControl.Resources>
<ListBox
ItemsSource="{Binding State, Converter={StaticResource EnumToValuesConverter}}"
SelectedItem="{Binding Path=State, Mode=TwoWay}" />
- Create the list in the XAML using a Singleton
ValueConverter
. Make the converter a Singleton (implement the Singleton pattern) by adding the following 2 lines to the converter:
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:
<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.
<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.
[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.
<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.
<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).
<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 string
s. Thus, the combo box, list box, radio buttons, are displaying string values as supplied from our converters.
First we write a quick InsertSpaces
function:
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).
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 string
s. The hard part here is the ConvertBack
routine, which converts the supplied string
to the enum
value.
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.
<UserControl.Resources>
<local:EnumToSpacedStringConverter x:Key="SpacedStringConverter" />
<local:EnumToSpacedStringsConverter x:Key="SpacedStringsConverter" />
The text block becomes:
<TextBlock Text="{Binding State, Converter={StaticResource SpacedStringConverter}}" />
The ListBox
and ComboBox
become:
<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):
<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:
<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