2

I have flag enum say this -

[Flags]
public enum Department
{
    None = 0,
    A = 1,
    B = 2,
    C = 4,
    D = 8
}

I want to show values of this enum on view. I thought of creating a listbox and binding its source to the collection of this enum List<Department> Departments. All works so good until i thought of having a checkbox which binds to a property on my Viewmodel -

public Department SelectedDepartments { get; set; }

The solution here http://compilewith.net/2008/12/wpf-flagsenumvalueconverter.html provides elegant solution for binding enum values to checkboxes but its have one limitation of creating checkboxes equal to the number of enum values in list. But, in my case i can't afford of having so many checkboxes lying on my UI since my Enum contains 20 values (so that means having 20 checkboxes on UI).

I tried using MultiBindingConverter but that fails in ConvertBack Method. I want to bind the state of checkboxes with property SelectedDepartments. Say if property value is "A | B" then A and B checkbox should be checked whereas C and D should remain unchecked.

Rohit Vats
  • 79,502
  • 12
  • 161
  • 185

2 Answers2

1

I don't think there's a way of doing this without using some code-behind.

I took the sample solution you linked to above, removed all of the CheckBoxes from MainWindow.xaml, added the following method to MainWindow.xaml.cs and called it from the MainWindow constructor:

    private void AddCheckBoxes()
    {
        var converter = new FlagsEnumValueConverter();
        foreach (Department dept in Enum.GetValues(typeof(Department)))
        {
            if (dept != Department.None)
            {
                var binding = new Binding()
                {
                    Path = new PropertyPath("Department"),
                    Converter = converter,
                    ConverterParameter = dept
                };

                var checkBox = new CheckBox() { Content = dept.ToString() };
                checkBox.SetBinding(CheckBox.IsCheckedProperty, binding);
                DepartmentsPanel.Children.Add(checkBox);
            }
        }
    }

This method does the work of creating all of the checkboxes, one for each named enum constant apart from None. I could then add further departments to the Department enum, rerun the solution and see additional checkboxes for the newly-added departments.

There were a few further minor changes that I had to make to this solution to get it working completely. You may or may not need to make these changes to your code. Firstly, I made the DataObject class implement INotifyPropertyChanged. Secondly, I rewrote the XAML in MainWindow.xaml as follows:

<StackPanel>
    <StackPanel x:Name="DepartmentsPanel" />
    <TextBlock Margin="5,20,0,0">
        <TextBlock Text="Raw Value:" FontWeight="Bold" />
        <TextBlock Text="{Binding Department}" />
    </TextBlock>
</StackPanel>

(Basically, I wrapped the existing DepartmentsPanel in another StackPanel and moved the 'Raw Value' display into this outer StackPanel.) Finally, I set the DataContext of the whole MainWindow, rather than the DataContext of the DepartmentsPanel, to the DataObject created. This step was necessary to make the 'Raw Value' display work.

Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
0

I have created an IValueConverter that supports binding to the enum directly without codebehind or helper classes. It has only two restrictions:

  • One converter instance has to be used for each source property. If the model contains more properties of the same enum type, each of them needs to use a separate converter instance. This can be done by instantiating the converters in the XAML.
  • When the enum type is a flags enum it has to be an Integer.

The solution is based on the empirical fact that there is always a Convert first before a ConvertBack. And there is always a Convert if a ConvertBack has changed the value. This one only works when the INotifyPropertyChanged is properly implemented on the model. So between the two calls the last known value can be stored in the converter and used in the ConvertBack method.

The converter instance should get the Type of the enum and whether it is a Flags enum or not.

 <EnumToCheckedConverter x:Key="InstanceName" Type="{x:Type MyEnum}" Flags="True" />

Then the checkboxes can be bound using this converter.

 <CheckBox Content="ValueText" IsChecked="{Binding Source, Converter={StaticResource InstanceName}, ConverterParameter=Value}"/>

Radio buttons can be bound by the same mechanism using an instance with Flags="False"

The source code of the converter

public class EnumToCheckedConverter : IValueConverter
{
    public Type Type { get; set; }
    public int? LastValue { get; private set; }
    public bool Flags { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && value.GetType() == Type)
        {
            try
            {
                var parameterValue = Enum.Parse(Type, parameter as string);

                if (Flags == true)
                {
                    var intParameter = (int)parameterValue;
                    var intValue = (int)value;
                    LastValue = intValue;

                    return (intValue & intParameter) == intParameter;
                }
                else
                {
                    return Equals(parameterValue, value);
                }
            }
            catch (ArgumentNullException)
            {
                return false;
            }
            catch (ArgumentException)
            {
                throw new NotSupportedException();
            }
        }
        else if (value == null)
        {
            return false;
        }

        throw new NotSupportedException();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && value is bool check)
        {
            if (check)
            {
                try
                {
                    if (Flags == true && LastValue.HasValue)
                    {
                        var parameterValue = Enum.Parse(Type, parameter as string);
                        var intParameter = (int)parameterValue;

                        return Enum.ToObject(Type, LastValue | intParameter);
                    }
                    else
                    {
                        return Enum.Parse(Type, parameter as string);
                    }
                }
                catch (ArgumentNullException)
                {
                    return Binding.DoNothing;
                }
                catch (ArgumentException)
                {
                    return Binding.DoNothing;
                }
            }
            else
            {
                try
                {
                    if (Flags == true && LastValue.HasValue)
                    {
                        var parameterValue = Enum.Parse(Type, parameter as string);
                        var intParameter = (int)parameterValue;

                        return Enum.ToObject(Type, LastValue ^ intParameter);
                    }
                    else
                    {
                        return Binding.DoNothing;
                    }
                }
                catch (ArgumentNullException)
                {
                    return Binding.DoNothing;
                }
                catch (ArgumentException)
                {
                    return Binding.DoNothing;
                }
            }
        }

        throw new NotSupportedException();
    }
}
Daniel Leiszen
  • 1,827
  • 20
  • 39