Tuesday 26 February 2019

[ReactiveUI] Bind a list of enums to WPF combo

Originally posted by: Mitkins, at: Oct 12 '17 at 0:22, at stackoverflow. I edited it to correct minor typos in the original. And to make one of two alterations.

I am trying to find a simple example where the enums are shown as is. All examples I have seen tries to add nice looking display strings but I don't want that complexity. Note: EffectStyle is just an enum.

public enum EffectStyle { Undefined = 0, Yes = 1, No = 2 }

Basically I have a class that holds all the properties that I bind, by first setting the DataContext to this class, and then specifying the binding like this in the xaml file:

<ComboBox ItemsSource="{Binding Path=EffectStyle}"/>

But this doesn't show the enum values in the ComboBox as items.


Using ReactiveUI, I've created the following alternate solution. It's not an elegant all-in-one solution, but I think at the very least it's readable.

In my case, binding a list of enum to a control is a rare case, so I don't need to scale the solution across the code base. However, the code can be made more generic by changing EffectStyleLookup.Item into an Object. I tested it with my code, no other modifications are necessary. Which means the one helper class could be applied to any enum list. Though that would reduce its readability - ReactiveList<EnumLookupHelper> doesn't have a great ring to it.

Using the following helper class:

public class EffectStyleLookup
{
    public EffectStyle Item { get; set; }
    public string Display { get { return Item.ToString();} }

    public EffectStyleLookup(){ }

    public EffectStyleLookup(EffectStyle item)
    {
        this.Item = item;
    }
}

Yes. I prefer using constructors. All things considered, you write less code.

In the ViewModel, convert the list of enums and expose it as a property:

public MyViewModel : ReactiveObject
{
  private ReactiveList<EffectStyleLookup> _effectStyles;
  public ReactiveList<EffectStyleLookup> EffectStyles
  {
    get { return _effectStyles; }
    set { this.RaiseAndSetIfChanged(ref _effectStyles, value); }
  }

  // See below for more on this
  private EffectStyle _selectedEffectStyle;
  public EffectStyle SelectedEffectStyle
  {
    get { return _selectedEffectStyle; }
    set { this.RaiseAndSetIfChanged(ref _selectedEffectStyle, value); }
  }

  public MyViewModel()
  {
    this.LoadEffectStyleCombo();
  }

  private void LoadEffectStyleCombo()
  {
    // Convert a list of enums into a ReactiveList
    var xs = ((IList<EffectStyle>)Enum.GetValues(typeof(EffectStyle)))
                  .Select( x => new EffectStyleLookup(x) );

    EffectStyles = new ReactiveList<EffectStyleLookup>();
    EffectStyles.AddRange(xs);
  }
}

In the ComboBox, utilise the SelectedValuePath property, to bind to the original enum value:

<ComboBox x:Name="cboEffectStyle" DisplayMemberPath="Display" SelectedValuePath="Item" />

In the View, this allows us to bind the original enum to the SelectedEffectStyle in the ViewModel, but display the ToString() value in the ComboBox:


public MyView()
{
    InitializeComponent();
    this.WhenActivated(disposables => this.BindControls(disposables));
}

private void BindControls(CompositeDisposable disposableRegistration)
{
    this.OneWayBind(ViewModel, vm => vm.EffectStyles, v => v.cboEffectStyle.ItemsSource).DisposeWith(disposableRegistration);
    this.Bind(ViewModel, vm => vm.SelectedEffectStyle, v => v.cboEffectStyle.SelectedValue).DisposeWith(disposableRegistration);
}