Click here to Skip to main content
12,450,101 members (51,371 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

9.1K views
308 downloads
9 bookmarked
Posted

Enumeration RadioButtion ListBox Control

, 3 Jan 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
An enumeration RadioButtion ListBox control.

Introduction

I have a background in Human Factors in computer systems, and I am a big fan of radio buttons. This is because users tend to prefer radio buttons 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 list box 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 radio buttons to be specified in the View. This is normally done because there is no control in WPF that allows radio buttons to be defined in the ViewModel. Therefore radio buttons 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 radio button. Of course, the text associated with a radio button 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 radio buttons 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 radio buttons, 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 value converter 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:

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:

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:

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 radio buttons if the enumeration type is changed, and to update the radio buttons if the enumerated value is changed:

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 radio buttons to operate correctly, a new class is required (the radio button ViewModel). It has a property for the state of the radio button, and a property for the text to be associated with each radio button. 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 radio button 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 radio button 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 radio button is clicked, and the class for the ViewModel for the radio button responds with an Action containing the enumerated value that was clicked, causing the following code to be executed:

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 radio button ViewModel, which causes the UpdateEnumerationValue method to be executed, updating all the radio buttons to be updated to correspond to the new EnumerationValue.

The class that is the radio button ViewModel is as follows:

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:

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 radio button 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:

<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:

<local:EnumRadioButtonListBox 
        EnumerationValue="{Binding SampleEnum,Mode=TwoWay}">
  <ListBox.Resources>
    <Style TargetType="RadioButton">
      <Setter Property="Margin" Value="2"/>
    </Style>

Here I put a style for radio buttons in the Resources for the control, and then set the margin for all radio buttons 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 radio button groups, and the only binding required is a single binding to the value that the multiple radio buttons will control. There is no need to have a property for each radio button in the View Model, 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 radio button 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)

Share

About the Author

Clifford Nelson
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 Clarity Medical in Pleasanton, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
NewsIt is a nice work Pin
NewPast.Net4-Jan-12 2:17
groupNewPast.Net4-Jan-12 2:17 
GeneralRe: It is a nice work Pin
Clifford Nelson10-Jan-12 14:04
memberClifford 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.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160826.1 | Last Updated 3 Jan 2012
Article Copyright 2012 by Clifford Nelson
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid