Click here to Skip to main content
15,868,141 members
Articles / Desktop Programming / WPF

Discovering the Limitations of IValueConverter in WPF

Rate me:
Please Sign up or sign in to vote.
4.65/5 (10 votes)
14 Dec 2011CPOL13 min read 29.9K   231   15   5
Discovering the limitations of IValueConverter in WPF

Introduction

I attempted to use a value converter in a way that it did not support. Although the solution does not do a perfect job of supporting binding to a single property in the ViewModel for RadioButton selection without needing to code the View with details about the enumeration, it does work for most cases, and it demonstrates a lot about the limitations of the IValueConverter interface. My work on this project showed me a lot of the limitations in value converters, and did create something that could be useful. Some of these lessons should be helpful to other developers.

History

I had created a way to handle enumerations for a ComboBox <code>using a value converter using the same property in the ViewModel for both ItemsSource and SelectedItem that works quite well. I only needed the property (enumeration) that was the value to be changed without needing another property for the list of options. ComboBoxes work well, but a more user friendly UI for the same functionality is to use Radio buttons. The reason that Radio buttons are preferred is that it is a quicker way for the user to choose an option, and the options are immediately obvious. The disadvantage is that radio buttons tend to take more space, and can.

When Radio buttons are normally used in a WPF View, each RadioButton has to be defined, including the title; this can be a maintenance nightmare. There are also problems with then associating the selection with the right enumeration when the radio buttons represent enumerated options, and thus saving problems with programming the right option.

A ComboBox is easier to work with since the result is a single value that is easier to associate with an enumeration. Creating a value converter for a ComboBox representing an enumeration is also much easier, and the enumeration DisplayAttribute can be used for the text display within the ComboBox (see my article, Using DescriptionAttribute for enumerations bound to a ComboBox).

With my wanting to create a better way to give the user a choice of options, and the success of doing the ComboBox value, I decided to see if there was a good way to build a value converter for a ListBox containing RadioButton controls. I wanted to do this using just basic WPF controls that could be defined on the form, and not use anything more sophisticated like a ControlTemplate. The design would be built around a value converter. Well, the way that Microsoft has implemented the value converter basically torpedoed my effort to create a generic way of creating radio button control that was driven by an enumeration using a value converter. However, the implementation can be used for a majority of cases where radio buttons are used, just a certain amount of care must be taken.

Implementation

The XAML for the control was pretty straight forward, using a ListBox using a RadioButton DataTemplate. However there is one gotcha:

XML
<ListBox ItemsSource="{Binding SampleEnum,
            Converter={StaticResource EnumDrivenCheckBoxConverter}}"
         SelectedItem="{Binding SampleEnum,
            Converter={StaticResource EnumDrivenCheckBoxConverter},Mode=TwoWay}"
         BorderThickness="0">
  <ListBox.ItemsPanel>
    <ItemsPanelTemplate>
      <StackPanel Orientation="Vertical"/>
    </ItemsPanelTemplate>
  </ListBox.ItemsPanel>
  <ListBox.Resources>
    <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
                     Color="#00000000" />
  </ListBox.Resources>
  <ListBox.ItemTemplate>
    <DataTemplate>
      <Grid>
        <RadioButton IsChecked="{Binding IsChecked}" Content="{Binding Text}" />
        <Border Background="#01000000" HorizontalAlignment="Stretch"
          VerticalAlignment="Stretch"/>
      </Grid>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

A big gotcha is that the CheckBox in the DataTemplate has to be covered by something to prevent the CheckBox action from interfering with the ListBoxItem selection. Without this covering (I used a border with an almost transparent Background), it is not possible to select the ListBoxItem; when an attempt is made to select an item, the CheckBox is selected/deselected instead. Not the functionality that is desired.

Another gotcha was the background of the selected ListBoxItem. I wanted this to behave like a group of radio buttons, not a ListBox containing radio buttons. The RadioButton selection was the only thing that was needed to identify which RadioButton was selected, and the ListBoxItem highlight was unnecessary.

I added the ItemsPanel in this code really just to show how it would be done if the standard ListBox was not appropriate. I also removed the border on the ListBox since I wanted this to look like a group of radio buttons and not a list containing radio buttons.

Now to convert this to a Style (minus the ItemsPanel), we have the following:

XML
<Style  x:Key="CheckBoxListBox" TargetType="ListBox">
  <Style.Resources>
    <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
      Color="#00000000" />
    <DataTemplate x:Key="CheckBoxItem">
      <Grid>
        <RadioButton IsChecked="{Binding IsChecked}" Content="{Binding Text}" />
        <Border Background="#01000000" HorizontalAlignment="
          Stretch" VerticalAlignment="Stretch"/>
      </Grid>
    </DataTemplate>
  </Style.Resources>
  <Setter Property="ItemsSource" Value="{Binding Path=DataContext,
    RelativeSource={RelativeSource Self},
    Converter={StaticResource EnumDrivenCheckBoxConverter}}"/>
  <Setter Property="SelectedItem" Value="{Binding Path=DataContext,
    RelativeSource={RelativeSource Self},
    Converter={StaticResource EnumDrivenCheckBoxConverter}, Mode=TwoWay}"/>
  <Setter Property="ListBox.ItemTemplate" Value="{StaticResource CheckBoxItem}"/>
  <Setter Property="BorderThickness" Value="0"/>
</Style>

Actually pretty simple since a ControlTemplate was not required. Including a Resources element in Style was part of the reason did not need a ControlTemplate, although could have defined the Resources outside of the Style, but I do not like that unless I believe I will need the resources in more than one place.

One of the really nice things about using a Style in this case, is that not only is it unnecessary to define the DataTemplate when the ListBox is used, but that the Binding is greatly simplified:

XML
<ListBox Style="{StaticResource CheckBoxListBox}"
    DataContext="{Binding SampleEnum, Mode=TwoWay}"/>

There is one very important thing when using this ListBox style and that is that the Binding defined for the DataContext must be TwoWay. If it is not, no matter what the Binding Mode on the SelectedItem for the ListBox in the Style, the bound value will not get updated.

Since this is a fairly simple implementation, pretty much anything that can be done with a normal ListBox can be done with this ListBox using standard XAML.

In the code, I also created a style for a ComboBox that is quite similar to the ListBox, and to change the ListBox to a ComboBox and the Style name with no other changes, the XAML is:

XML
<ComboBox Style="{StaticResource CheckBoxComboBox}"
    DataContext="{Binding SampleEnum, Mode=TwoWay}"/>

The XAML part was the easy part—Sort of had the idea of using a ListBox with radio buttons from the beginning. One of the bindings was also obvious; The ItemsSource would have to be implemented. In order to bind to the radio buttons in the ListBox, I would need to bind to both the Content and the IsChecked properties. This meant that I would have to create a new class for the ItemsSource binding that was generated by the value passed in the Convert method. Since I wanted a lot of flexibility in the text displayed with each CheckBox, I designed the converter to use the DisplayAttribute for the specific enumeration when it is available, otherwise just use the enumeration name:

C#
var list = new List<EnumDrivenRadioButtonBinding>();
  
foreach (var value in Enum.GetValues(e))
{
    FieldInfo info = value.GetType().GetField(value.ToString());
    var valueDescription = (DescriptionAttribute[])info.GetCustomAttributes
              (typeof(DescriptionAttribute), false);
    list.Add(new EnumDrivenRadioButtonBinding(value,
         valueDescription.Length == 1 ?
            valueDescription[0].Description : value.ToString()));
}

The class for each of the enumeration values is:

C#
    public class EnumDrivenRadioButtonBinding : INotifyPropertyChanged
    {
    public string Text { get; private set; }
    public bool IsChecked { get; set; }
    public object Enumeration { get; private set; }

    internal EnumDrivenRadioButtonBinding(object value, string description)
    {
      Text = description;
      Enumeration = value;
    }

    internal void UpdateIsChecked (bool value)
    {
      if (value != IsChecked)
      {
        IsChecked = value;
        if (PropertyChanged != null)
          PropertyChanged (this, new PropertyChangedEventArgs("IsChecked"));
      }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Notice that it is derived from INotifyPropertyChanged so that changes in the IsChecked property will be propagated to the View. I have a constructor that sets the text to display and the actual enumeration value so that these can be readonly, and thus protected. There is no need to use a backing field for the IsChecked property since it is updated by the UpdateIsChecked method. Basically, when the bound value is updated, it is then read again, and so whatever the View sets as the value would be set again when the bound value in the ViewModel is read back. This is how the previously set value is reset.

My initial attempt was to put all of the handling of state in this EnumDrivenRadioButtonBinding class, there was no good way, even with call backs, to update the bound value in the ViewModel. The only way I could figure to get the ViewModel value updated was to bind to either the SelectedItem or SelectedIndex of the ListBox. I decided to use the SelectedItem. Because both ItemsSource and SelectedItem need to have access to the same collection of the EnumDrivenRadioButtonBinding instances, the same value converter had to be used. This was easy enough to resolve since all that was required was to check if the targetType argument for the Convert method was IEnumerable. If it is, the list is returned (this would be for the ItemsSource), otherwise the items in the list are updated so that only the IsChecked property of class instance for the one enumeration is set to true. The convert method would be as follows:

C#
public object Convert(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
{
  if (value == null || !value.GetType().IsEnum)
    return value;
  if (!_localLists.ContainsKey(value.GetType()))
    CreateList(value.GetType());

  if (targetType.Name == "IEnumerable") //ItemsSource
  {
    return _localLists[value.GetType()];
  }
  else //SelectedItem
  {
    foreach (var item in _localLists[value.GetType()])
      item.UpdateIsChecked(item.Enumeration.Equals(value));
    //this is irrelevant
    return null;
  }
}

You will notice that the actual list used is stored in a dictionary that has a key of the type of the value argument. The problem with value converters is that the same value converter is used for all instances that use the converter. As can be seen in the code above, I need to have list for binding the ItemsSource, which is also the data the code uses for setting of the radio buttons.

If there are two sets of this radio button ListBox’s, then both are using the same instance of the converter. This means that they will interfere with each other. The way that this is resolved in this code is the use of a dictionary using the key of the enumeration type. In the example, I open two instance of the “Value Converter Interference of Two Controls” window when the “Launch Basic Example” button on the main window is clicked. This shows that the problem does not occur between two windows.

I also wrapped the RadioButton ListBox and ComboBox in a UserControl, and put two instances of this UserControl in a window. The two UserControls were completely independent, although problems still persisted within each UserControl. Click the “LaunchUserControl Example” on the main window to see this example. Putting the RadioButton ListBox control in a DataTemplate of another ListBox showed that the problem persisted, and disappeared when the DataTemplate contained the UserControl containing the CheckBox ListBox. These examples can also be seen by clicking buttons on the main window of the example.

In most cases, the interference is not a problem, but I can easily see problems if there are multiple Yes/No RadioButton selections. Now if I do not try to define the ListBox in a Style and use this style to do all the binding, then I can just use the ConverterParameter to differentiate different controls. However, this could still have problems if this ListBox control is within a DataTemplate, but shows that for many situations the implementation will work just fine.

Lessons Learned

This effort showed up the weaknesses of the IValueConverter implementation used by Microsoft.

The first problem is that an instance of the IValueConverter class is reused for all controls on a form that use the converter. In simple circumstances, this would be fine, but where there is state information, it complicates the solution, or even makes it so that it is impossible to implement a solution using IValueConverter. I had successfully gotten around this solution before by using information about the Type being bound to, and that worked as long as that was adequate as long as that was the only dependency, in other words, if the behavior only depended on the type, not the instance of the type.

I did try a number of tricks to try to overcome this problem, but no matter what I did, the same instance of the converter was used within the form. This included moving the definition of the converter inside the style.

The same instance of the value converter is not used in a second window. Therefore, the designer does not need to worry about it.

The example does a good job of showing the issues with using value converters.

  • Using the same resource key for different controls still uses the same instance of the value converter (different keys will be different instances of the value converter).
  • There are two identical windows, and this demonstrates that the problem does not occur across Windows.
  • UserControls also have their own instance of the value converter.

I have both a ComboBox and a ListBox defined to use the same value converter. In the example, the only difference is the name of the control. This shows it is easy to convert between the two with the code, which is nice since sometimes it is hard to sell the radio buttons, even though users tend to prefer them. This way can code for ComboBoxes and then with the change of one word, go to RadioButtons.

There is also a good bit of commented out code and XAML to allow playing with options to see the behavior of value converters.

Options for Fixing the Problem

One of the immediate ideas I had for fixing the problem was using a multi value converter. The idea would be to have one of the values bound to the enumeration, and the other to the control itself. That works just fine for the convert since all the values are available, but not for the convert back, in which case only a single value is available. In truth, almost every time you probably use the IMultValueConveter, you are not coding the ConvertBack method, just throwing an exception.

Using the parameter argument would be another option, but the parameter argument is not a DependencyProperty, so there is no dynamic nature to this value. There are many posts on people wanting to use the parameter argument in a dynamic way, and have been frustrated by this limitation. Sometimes the IMultiValueConverter can be used to get around this limitation with the parameter argument.

Another option is to have the same value converter defined with different keys. This works, but really no better than using the parameter since I would have to move the specific bindings for SelectedItem and ItemsSource out of the style.

I also looked at adding a DependencyProperty to the value converter. This is another very good way to get around the limitations of the parameter argument, but for this requirements, there appeared to be no good way to deal with getting a reference from the control using the value converter; another brick wall.

I also attempted to inherit from MarkupExtension, but that proved to have exactly the same problems as the value converters, which should not really be a surprise.

A UserControl can contain the RadioButton ListBox, but I do not consider this a satisfactory solution, and would prefer to create a RadioButton ListBox in code instead.

Conclusion

The value converter as implemented, with the specific bindings defined in a ListBox style, will work just fine as long as there are no two controls that are bound to values with the same enumeration type within the form. Another solution would be to move the binding definitions for ItemsSource and SelectedItem out of the style and use the ConverterParameter to ensure uniqueness (or use different keys for the value converter). This still will have problems if the ListBox is used within a DataTemplate and would eliminate the elegance of hiding the complexity of the binding within the style.

What would work is if Microsoft made the parameter argument a DependencyProperty. Then reference to the control using the value converter could be passed to the Convert and ConvertBack methods. In my mind, Microsoft should fix the problem of the parameter not being a DependencyPropety since it does limit the usefulness of the IValueConverter. Normally events have a sender argument, which the methods for IValueConverter do not. Having a sender argument in events has proven to be extremely useful, and I think that few would support removing this argument from events. If there was a sender argument for the value converter, then this problem would have been even more easily solved, and would a number of other issues with using value converters within the Microsoft community.

I would not have posted this article, and would have just created an implementation that was perfect, but I felt that there were lessons in the failure of this approach, and the example did a great job of showing the behavior and limitations of the IValueConverter; a RadioButton ListBox that is universally useful could not use a value converter as I would have liked. It is also interesting in that I did a binding to the DataContext of the ListBox and then used this binding within the style.

History

  • 13th December, 2011: Initial version

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

 
QuestionUsing x:Shared is brilliant -- Thanks AspDotNetDev .. Pin
Quiz Arena11-Jun-19 16:09
Quiz Arena11-Jun-19 16:09 
GeneralMultiple Instances of an IValueConverter Pin
AspDotNetDev14-Dec-11 8:15
protectorAspDotNetDev14-Dec-11 8:15 
GeneralRe: Multiple Instances of an IValueConverter Pin
Clifford Nelson14-Dec-11 11:07
Clifford Nelson14-Dec-11 11:07 
GeneralRe: Multiple Instances of an IValueConverter Pin
cyberdude26-Apr-12 5:03
cyberdude26-Apr-12 5:03 
GeneralRe: Multiple Instances of an IValueConverter Pin
Clifford Nelson26-Apr-12 7:43
Clifford Nelson26-Apr-12 7:43 

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.