Click here to Skip to main content
15,867,973 members
Articles / Desktop Programming / WPF

Enumeration RadioButton ListBox Control

Rate me:
Please Sign up or sign in to vote.
5.00/5 (11 votes)
3 Jan 2012CPOL8 min read 22.5K   410   19   3
An enumeration RadioButtion ListBox control.

Introduction

I have a background in Human Factors in computer systems, and I am a big fan of RadioButton controls. This is because users tend to prefer RadioButton controls to other input options for multiple choices, at least for a small number of choices. There are two good reasons for this: faster input (single click as opposed to two clicks for a combo box), and the options are readily obvious (which also makes it faster). Of course a ListBox can be used for the same thing, but the visual impact is different.

One of the things that is undesirable in WPF is the need for RadioButton controls to be specified in the View. This is normally done because there is no control in the ViewModel. Therefore RadioButton controls are almost always defined in the View, with all the associated maintenance headaches of defining them in the View and ensuring that the View and ViewModel are aligned correctly as to what the purpose of each RadioButton. Of course, the text associated with a RadioButton can use binding to either a static value or a property in the ViewModel (which I believe adds too much to the ViewModel).

Probably the best way to define a group of associated RadioButton controls that will be combined into a single control is to be represented as an enumeration. The problems with this approach are the support of only a subset of the enumeration values or a requirement for dynamic behavior. This immediately means that a single binding cannot be used, and if the options are totally dynamic, the advantages of using an enumeration to drive the control are lost. For the simplest, and most numerous case, where the control will provide all selections from an enumeration, a control can be created that looks like RadioButton controls, and is as generic as a standard ListBox containing RadioButton controls, and only requires a single binding to an enumerated value.

Implementation

I had previously worked on using value converters to do the same thing, but there were some serious limitations having to do with reuse of value converters in containers. It also required both the an IValueConverter and some XAML that either had to be replicated for each use, or using a style. Creating a control that inherits from a ListBox is far superior and requires about the same amount of code, and still it provides a great deal of flexibility in customization using XAML.

The trick in creating something that is easy to use without requiring XAML backing is creating the DataTemplate in the constructor. This DataTemplate is required to define the RadioButton for each enumerated value. Microsoft originally had provided a FrameworkElementFactory class for building DataTemplates (for some reason, Microsoft in their wisdom, did not see fit to allow DataTemplates to be created using standard controls). Using a FrameworkElementFactory is cumbersome, and requires all controls within the DataTemplate to be added as a FrameworkElementFactory. Apparently, this approach could not do everything that could be done in XAML, so Microsoft’s new recommendation is to create the DataTemplate in XAML and use the XamlReader to parse the string:

C#
public DataTemplate RadioButtonDataTemplate()
{
  var xaml = @"<DataTemplate 
    xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
          <Grid>
            <RadioButton IsChecked=""{Binding IsChecked}"" 
                 Content=""{Binding Text}"" />
          </Grid>
        </DataTemplate>";
  object load = XamlReader.Parse(xaml);
  return (DataTemplate)load;
}

So in the control’s constructor, I just set the ItemTemplate to this DataTemplate that was dynamically created and set the BorderThickness for the ListBox to “0” so that the ListBox border does not appear:

C#
public EnumRadioButtonListBox()
{
  ItemTemplate = RadioButtonDataTemplate();
  BorderThickness = new Thickness(0);
}

The only other thing I need for this ListBox is the DependencyProperty for the enumerated value. I did not want to use the ItemsSource or SelectedItem for the EnumerationValue in part because the DependencyProperty would be used for both, so neither was really appropriate, and using a separate DependencyProperty meant that I would not interfere with the operation of the existing properties:

C#
public object EnumerationValue
{
  get { return (object)GetValue(EnumerationValueProperty); }
  set { SetValue(EnumerationValueProperty, value); }
}

public static readonly DependencyProperty EnumerationValueProperty =
    DependencyProperty.Register("EnumerationValue", 
    typeof(object), typeof(EnumRadioButtonListBox), 
    new UIPropertyMetadata(new PropertyChangedCallback(EnumerationChanged)));

Notice that there is a property changed callback defined for the DependencyProperty. This is obviously required since it is necessary to respond to the changes in the enumerated value to update the list of RadioButton controls if the enumeration type is changed, and to update the RadioButton controls if the enumerated value is changed:

C#
private static void EnumerationChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
{
  if (e.NewValue == null)
    return;
  ((EnumRadioButtonListBox)d).UpdateEnumerationValue(e.NewValue);
}

    private void UpdateEnumerationValue(object value)
{
  if (!value.GetType().IsEnum)
    throw new Exception(string.Format(
      "The type '{0}' is not an Enum type, and is not supported for " +
      "EnumerationValue", value.GetType()));
  if (value.GetType() != _listType)
  {
    _listType = value.GetType();
    _list = new List<EnumDrivenRadioButtonBinding>();
    foreach (var item in Enum.GetValues(_listType))
      _list.Add(new EnumDrivenRadioButtonBinding(item, EnumerationChanged));
    ItemsSource = _list;
  }

      if (_value == null || !value.Equals(_value))
  {
    foreach (var item in _list)
      item.UpdateIsChecked(value);
    _value = value;
  }
}

The first thing that is done is to ensure that the type of the value is an enumeration since there is no point continuing if it is not. An exception is thrown if the value is not an enumeration.

Next a check is made to see if the enumeration type has changed so that the IEnumerable for the ItemsSource corresponds to the values for the enumeration type. So that the options in the list box correspond to the current enumeration, the list has to be updated each time the enumeration type is changed. To allow the RadioButton controls to operate correctly, a new class is required (the RadioButton ViewModel). It has a property for the state of the radio button, and a property for the text to be associated with each RadioButton. In the constructor for this class, the particular enumerated value and a pointer to an event handler are passed. From the value, the class can get the text to associate with the radio button, and also now we will have the value associated with it so that it can provide this value in the delegate when the  is selected. The address of the delegate to handle the event is the second argument in the constructor. An instance of this class is created for each enumeration value, and is added to the enumeration that is the ItemsSource for the control.

The last part of this code is responsible for ensuring that the RadioButton in the list that is selected corresponds to the value in the DependencyProperty EnumerationValue. It also is the code that responds to user input since when a RadioButton is clicked, and the class for the ViewModel for the RadioButton responds with an Action containing the enumerated value that was clicked, causing the following code to be executed:

C#
private void EnumerationChanged(object newValue)
{
  EnumerationValue = newValue;
  if (PropertyChanged != null)
    PropertyChanged(this, new PropertyChangedEventArgs("EnumerationValue"));
}

This code updates the EnumerationValue with the value provided by the RadioButton ViewModel, which causes the UpdateEnumerationValue method to be executed, updating all the RadioButton controls to be updated to correspond to the new EnumerationValue.

The class that is the RadioButton ViewModel is as follows:

C#
public class EnumDrivenRadioButtonBinding : INotifyPropertyChanged
{
  public string Text { get; private set; }

      public bool IsChecked
  {
    get { return _isChecked; }
    set
    {
      //Only need to change to true
      _isChecked = true;
      _isCheckedChangedCallback(_enumeration);
    }
  }

      private readonly object _enumeration;
  private bool _isChecked;
  private readonly Action<object> _isCheckedChangedCallback;

      internal EnumDrivenRadioButtonBinding(object value, 
        Action<object> isCheckedChangedCallback)
  {
    FieldInfo info = value.GetType().GetField(value.ToString());
    var valueDescription = (DescriptionAttribute[])info.GetCustomAttributes
              (typeof(DescriptionAttribute), false);

        Text = valueDescription.Length == 1 ?
            valueDescription[0].Description : value.ToString();
    _enumeration = value;
    _isCheckedChangedCallback += isCheckedChangedCallback;
  }
 
      internal void UpdateIsChecked(object value)
  {
    if (_enumeration.Equals(value) != IsChecked)
    {
      _isChecked = _enumeration.Equals(value);
      if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
    }
  }

      public event PropertyChangedEventHandler PropertyChanged;

      public override string ToString()
  {
    if (_isChecked)
      return "true - " + Text;
    return "false - " + Text;
  }
}

A lot of the intelligence is in this class. The constructor determines if the enumeration value has a DescriptionAttribute, and if it does, uses that for the title, otherwise the enumeration value name is used.  An example of an enumeration that includes description attributes is as follows:

C#
public enum SampleEnum
{
    [DescriptionAttribute("I like the color blue")]
    Blue,
    [DescriptionAttribute("I like the color green")]
    Green,
    [DescriptionAttribute("I like the color yellow")]
    Yellow,
    Orange,
    [DescriptionAttribute("I like the color red")]
    Red
}

All the enumeration types above have a DescriptionAttribute except the Orange enumeration type. In this case, the instance of the class for every enumeration type will have a text value equal to the associated DescriptionAttribute except the instance for the Orange enumeration value, which will be the value “Orange”. The constructor also saves the enumeration value which is returned as an argument in the saved handler delegate that is executed whenever the IsChecked value becomes true.

The RadioButton ViewModel also contains a method UpdateIsChecked that checks if the passed argument is equal to the instance’s enumeration value, and ensures that the IsChecked value is only true if this is equal.

You will note that I use the Equals method when comparing values. I have found that “==” often does not work. This may be because I am dealing with what the compiler sees and an object, and not what the object contains. Only the Equals method is reliable.

An interesting note is that the IsChecked property never uses the value when setting the new value since the only time that the IsChecked will be triggered by the UI is when going from false to true. This is also why the callback delegate only needs an enumeration value argument and not a checked argument, it will always be true.

Using the Control

The nice thing about this control is that only a single binding of EnumerationValue to the enumeration in the ViewModel is required. At its simplest, the XAML to use this Control is:

XML
<local:EnumRadioButtonListBox EnumerationValue="{Binding SampleEnum,Mode=TwoWay}"/>

Note: The TwoWay binding mode is required for this Control to work right.

The example uses slightly different XAML to show how standard XAML changes to the ListBox and RadioButton controls can be used to customize this control in many ways:

C#
<local:EnumRadioButtonListBox 
        EnumerationValue="{Binding SampleEnum,Mode=TwoWay}">
  <ListBox.Resources>
    <Style TargetType="RadioButton">
      <Setter Property="Margin" Value="2"/>
    </Style>
  </ListBox.Resources>
  <ListBox.ItemsPanel>
    <ItemsPanelTemplate>
      <WrapPanel Width="200" IsItemsHost="True" />
    </ItemsPanelTemplate>
  </ListBox.ItemsPanel>
</local:EnumRadioButtonListBox>

Here I put a style for RadioButton in the Resources for the control, and then set the margin for the RadioButton to “2.” The ListBox is customized the way any ListBox would be, in this case changed the ItemsPanel to a WrapPanel.

Conclusion

This should be a useful control in many applications since it supports using enumerations to define RadioButton groups, and the only binding required is a single binding to the value that the multiple RadioButton controls will control. There is no need to have a property for each radio button in the ViewModel, nor is there a requirement for an ItemsSource binding.

In addition, the DescriptionAttribute associated with each enumeration value is used for the text associated with each RadioButton if one is defined. It is nice being able to define the text as something besides the name of the enumeration value since no special characters, including spaces, can be used in the name. Also, enumeration values may want to follow some naming conventions.

License

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


Written By
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

Comments and Discussions

 
QuestionMy vote of 5 Pin
CapGroupAccess26-Oct-18 8:15
CapGroupAccess26-Oct-18 8:15 
NewsIt is a nice work Pin
NewPast4-Jan-12 2:17
NewPast4-Jan-12 2:17 
GeneralRe: It is a nice work Pin
Clifford Nelson10-Jan-12 14:04
Clifford Nelson10-Jan-12 14:04 

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.