Search code examples
c#wpfxamlcomboboxivalueconverter

How to bind combo box to another value than the actual by offsetting it?


I have a combo box marked up as follows.

<ComboBox SelectedIndex="{Binding Path=Bonkey}">
  <ComboBoxItem Content="Monkey" />
  <ComboBoxItem Content="Donkey" />
</ComboBox>

I'm binding to an object that has Bonkey field types as integer, according to the declaration below.

class Thingy
{
  public int Bonkey {get; set; }
  ...
}

Although it works great and as supposed to, there's a programmatically technical issue keeping me up at night. The indexes generated in the manual mark up are 0 and 1. However, I know that the values of the integers will be 1 and 2. (I.e. Monkey is indexed by 0 when related to combo box item but it's actual value in the object used as data source is 1. Analogously, Monkey has index 1 in the items of the combo box but it corresponds to 2 in the object.)

My intermediate solution was to knock off 1 in the constructor right before setting the data context, followed by a kick up by 1 when the view is being disposed. It's working but I can't really be proud, so to speak.

public SomeDialog(Thingy thingy)
{
  InitializeComponent();
  thingy.Bonkey--;
  DataContext = thingy;
}
...
private void Cancel_Click(object sender, RoutedEventArgs eventArgs)
{
  DialogResult = false;
  DataContext.Bonkey++;
  Close();
}
...
private void Submit_Click(object sender, RoutedEventArgs eventArgs)
{
  DataContext.Bonkey++;
  ...
}

How can I do that more, well... unshamely?

  1. I couldn't locate any attributes to explicitly set the indexes of the items in the combo box.
  2. I've tried using converters but somehow, I either got empty stuff in the combo box (no pre-selection) or some weird error message telling me that I made a boo-boo (and since I wasn't even sure if this is a good approach I let it go).
  3. Googling gave a bunch of results, none of which gave me clarity of how to offset the value bound (although to be honest, I suspect that I might be using too weird key words, because it seems that such an issue must've been discussed before).

Solution

  • There are a number of questions involving offsets and IValueConverter implementations. But browsing through them, I don't see one that addresses the specific scenario of binding with an offset; many of the questions involve people who already have the converter working but are having other problems, and others involve scenarios that are in one way or another more complex than this one.

    So, here's a very simple offset converter implementation:

    class OffsetValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            int offset = int.Parse((string)parameter);
    
            return (int)value - offset;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            int offset = int.Parse((string)parameter);
    
            return (int)value + offset;
        }
    }
    

    Used like this:

    <ComboBox SelectedIndex="{Binding OffsetValue,
                              Converter={StaticResource offsetValueConverter1},
                              ConverterParameter=1}"/>
    

    Where, of course, you have a resource declared to make an instance of the converter available, e.g.:

    <Window.Resources>
      <l:OffsetValueConverter x:Key="offsetValueConverter1"/>
    </Window.Resources>
    

    There are other options for implementation, such as giving the converter instance itself a property to set to control the offset, or specifying the offset as an actual int value so it didn't have to be parsed, but these approaches have their own limitations, such as not being able to reuse the same instance for different offsets, or requiring a more verbose declaration in XAML, respectively. I think the above strikes a good balance between convenience and efficiency.


    See also related questions:
    How can I bind one property to another property, offset by a specific amount?
    Applying transforms from DataTemplates in WPF

    There are others, but these seem the most closely related to your own scenario.