Binding Flagged Enumerations

Introduction

Recently a tweet brought my attention to this blog post by @san0x, regarding the difficulties of binding flagged enumerations to UI fields in a meaningful and easy-to-use fashion.  The post was largely dealing with ways to work around the limitation of the IValueConverter, and its ‘context agnostic’ processing of values during the conversion process.  The IValueConverter does not know the context of the value being converted, only the value itself and an [unbindable] conversion parameter.

Scenario

I’ll avoid going into great detail about the scenario here, as it’s explained in detail in the linked post above, but the main focus is binding a series of checkboxes to a single flagged enumeration value.  The classes I’ll use in my example are adapted from the original post, I’ve included them here as a convenience to the reader.

    public class Species : ViewModelBase
    {
        /// 
        /// The  property's name.
        /// 
        public const string NamePropertyName = "Name";

        private string _name = String.Empty;

        /// 
        /// The species name
        /// 
        public string Name
        {
            get
            {
                return _name;
            }

            set
            {
                if (_name == value)
                {
                    return;
                }

                var oldValue = _name;
                _name = value;

                // Update bindings, no broadcast
                RaisePropertyChanged(NamePropertyName);
            }
        }

        /// 
        /// The  property's name.
        /// 
        public const string FlagsPropertyName = "Flags";

        private SpeciesFlag _flags = SpeciesFlag.None;

        /// 
        /// The properties of the species, as a flagged enumeration
        /// 
        public SpeciesFlag Flags
        {
            get
            {
                return _flags;
            }

            set
            {
                if (_flags == value)
                {
                    return;
                }

                var oldValue = _flags;
                _flags = value;

                // Update bindings, no broadcast
                RaisePropertyChanged(FlagsPropertyName);
            }
        }
    }
    [Flags]
    public enum SpeciesFlag : byte
    {
        None = 0,
        BreathesAir = 0x1,
        BreathesWater = 0x2,
        Photosynthesizes = 0x4,
        Eats = 0x8
    }

Note: My examples use the excellent MVVM Light Toolkit by @LBugnion, but are NOT intended to be examples of best practices when using the toolkit

 

Potential Solutions

Many developers [myself included], when presented with these types of problems, take a similar route to @san0x, attempting to coerce the value converter into something that is context-aware in any number of ways.  My personal investigations led me to first investigate using WPF MultiBinding [a little used, but occasionally very handy feature].  The promise of the MultiBinding was that I could pass in a number of bound values, and get back a suitable result [true/false].  As things usually go [for me at least], this worked fabulously for the first 50% of the problem, binding from the source –> the CheckBox.IsSelected property worked just as expected with a binding very similar to this:

<CheckBox.IsChecked>
    <MultiBinding Mode="TwoWay" 
                   Converter="{StaticResource FlaggedEnumToBoolMultiConverter}" ConverterParameter="BreathesAir">
      <Binding Path="Species" />
      <Binding Path="Species.Flags" />
    </MultiBinding>
</CheckBox.IsChecked>

 

This allowed me to reflect the flag value on the CheckBox, and changes to the value were automatically reflected on the UI without a lot of additional work.  The issue, of course, was how to update the underlying object when the CheckBox itself is used to change the value.  This is much harder, because the IMultiValueConverter::ConvertBack method has two problems:

  1. Only a single [boolean] value, and [object] parameter are passed to the ConvertBack method.
  2. ConvertBack must pass back an array of values, one for each Binding contained in the MultiBinding.

So… we’re stuck… there’s no way to convert from a single boolean back into a Species and its flags while only effecting the single ‘bit’ or ‘bits’ we care about.  We simply do not have enough information available to us as the time of conversion.  Sure, there are workarounds, and one such workaround is listed in @san0x’s follow-up post [using a standard IValueConverter], but in the end, those workarounds all ‘smell’ like hacks to me.  They all involve keeping the context of the binding around in some fashion during conversion.

Reframing the Problem

My suggestion on Twitter was to utilize the ViewModel to expose the individual flags as bindable properties to the View.  This successfully reframes the problem, but does suffer from the requirement that you update your ViewModel every time your enumeration changes [the View needs to be updated with any solution, since we’re using individual CheckBox(s)].  This ‘smells’ better to me simply because it does what ViewModel’s are supposed to do, abstract away the underlying model when necessary to make data binding feasible.  It also doesn’t resort to any of the same hacks required to get binding to work properly, and context is retained because it’s handled by the bound view model.

Proposed Solution

So with the reframing of the question in mind, I moved on to whether or not it would be possible to simplify the process of binding to that flagged enumeration.  I wanted to minimize coding effort, and have the solution work for any general flagged enumeration.  Back in the .NET 1.x/2.x days, I had worked on a number of projects that leveraged the ICustomTypeDescriptor and it’s siblings to provide customized design and run-time behavior for objects and properties.  Silverlight 5 and .NET 4.5 bring the ICustomTypeProvider to bear on the problem, and could be used in those environments.  For this post, since we’re running WPF and .NET 4.0, I’m going to utilize DynamicObject to provide for runtime binding of the flagged enumeration values.

The FlaggedEnumViewModel Class

I’ll start by introducing the class that enables runtime binding and implements the bits necessary for the bindings to resolve properly:

    public class FlaggedEnumViewModel<TEnum>: DynamicObject, INotifyPropertyChanged
    {
        private Func<TEnum> _getter;
        private Action<TEnum> _setter;

        public FlaggedEnumViewModel(Func<TEnum> getter, Action<TEnum> setter)
        {
            if (!typeof(TEnum).IsEnum) 
                throw new ArgumentException("This type must only be used with enumerations.");

            _getter = getter;
            _setter = setter;
        }

        public override DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter)
        {
            return base.GetMetaObject(parameter);
        }
        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return Enum.GetNames(typeof(TEnum)).Select(i => "Has" + i);
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (!binder.Name.ToLower().StartsWith("has") '' binder.Name.Length < 4)
            {
                result = null;
                return false;
            }

            long checkedFlag = Convert.ToInt64(Enum.Parse(typeof(TEnum), binder.Name.Substring(3), true));
            long currentValue = Convert.ToInt64(_getter());

            result = (checkedFlag & currentValue) != 0;
            return true;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            if (!binder.Name.ToLower().StartsWith("has") '' binder.Name.Length < 4)
            {
                return false;
            }

            long checkedFlag = Convert.ToInt64(Enum.Parse(typeof(TEnum), binder.Name.Substring(3), true));
            long currentValue = Convert.ToInt64(_getter());

            if ((bool)value == true)
            {
                currentValue '= checkedFlag;
            }
            else
            {
                currentValue &= ~checkedFlag;
            }

            _setter((TEnum)Enum.ToObject(typeof(TEnum), currentValue));

            OnPropertyChanged(binder.Name);

            return true;
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

 

In the above code, I’ll call attention to three bits:

  1. The constructor takes a ‘getter’ and ‘setter’ argument that wraps access to the underlying flagged enum store.
  2. The TryGetMember method handles converting from the flagged value to a boolean
  3. The TrySetMember method handles converting from the boolean back to the flagged value

 

The View/ViewModel

Utilizing the class is fairly straightforward, in my ViewModel, I’ve exposed a property [WrappedFlags] of the FlaggedEnumViewModel type that I use for binding on the UI, and the ‘Species’ property notifies that both properties change whenever the Species property changes.  This is sufficient to enable TwoWay binding on the View.

    public class MainViewModel : ViewModelBase
    {
        public string Welcome
        {
            get
            {
                return "Sample Flagged Enum Editor";
            }
        }

        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            if (IsInDesignMode)
            {
                // Code runs in Blend --> create design time data.
            }
            else
            {
                // Code runs "for real"
            }

            LoadRandomSpecies = new RelayCommand(() => AssignRandomSpecies());
            WrappedFlags = new FlaggedEnumViewModel<SpeciesFlag>(
                                   () => Species.Flags, 
                                   (v) => Species.Flags = v);
        }

        /// <summary>
        /// The <see cref="Species" /> property's name.
        /// </summary>
        public const string SpeciesPropertyName = "Species";

        private Species _species = new Species() { Name = "Sample Species" };

        /// <summary>
        /// The Species we're working with
        /// </summary>
        public Species Species
        {
            get
            {
                return _species;
            }

            set
            {
                if (_species == value)
                {
                    return;
                }

                var oldValue = _species;
                _species = value;

                // Update bindings, no broadcast
                RaisePropertyChanged(SpeciesPropertyName);
                RaisePropertyChanged("WrappedFlags");
            }
        }

        public FlaggedEnumViewModel<SpeciesFlag> WrappedFlags { get; private set; }

        public RelayCommand LoadRandomSpecies { get; private set; }

        protected void AssignRandomSpecies()
        {
            Random rnd = new Random();
            byte value = (byte)rnd.Next(0, 16);

            Species = new Species()
            {
                Name = "Random Species " + value.ToString(),
                Flags = (SpeciesFlag)value
            };
        }
    }

 

Lastly, the view code is pretty basic, I’ll just call out one of the CheckBox elements to show how the binding was done:

    <CheckBox Content="Breathes Air" 
               IsChecked="{Binding Path=WrappedFlags.HasBreathesAir, Mode=TwoWay}" />

 

Conclusion

Transitioning from a converter-based approach to enhancing the ViewModel makes the problem much easier to implement and troubleshoot, it also avoids many of the pitfalls of introducing state to your I[Multi]ValueConverter instances.  Utilizing a DynamicObject class derivative enables you to write much less code and get the same overall benefit at runtime.  In a future post, I hope to find time to walk through enhancing the design-time experience to support setting up bindings in the VS2010 XAML designer.  I look forward to hearing your comments, or seeing alternative solutions to the problem.

Thanks for reading!

Sample Code: WPFBindingFlaggedEnum-20111013.zip (19 kb)