0

I am trying to bind WPF ComboBox to a collection of enum values using MarkupExtension from this answer https://stackoverflow.com/a/4398752/964478

It works fine for {Binding Source={my:Enumeration {x:Type my:Status}}} but I want to use a collection of Enum values instead of all values.

    public IList<Status> Statuses
    {
        get
        {
            return new[]
            {
                Status.Available,
                Status.Away
            };
        }
    }

I added a constructor for that

  public class EnumerationExtension : MarkupExtension
  {
       private Type _enumType;

        private Enum[] _enumValues;

        public EnumerationExtension(IEnumerable<Enum> enumValues)
        {
            _enumValues = enumValues.ToArray();

            EnumType = _enumValues.First().GetType();
        }

        //public EnumerationExtension(Type enumType)
        //{
        //    if (enumType == null)
        //        throw new ArgumentNullException(nameof(enumType));

        //    EnumType = enumType;
        //}

        public Type EnumType
        {
            get { return _enumType; }
            private set
            {
                if (_enumType == value)
                    return;

                var enumType = Nullable.GetUnderlyingType(value) ?? value;

                if (enumType.IsEnum == false)
                    throw new ArgumentException("Type must be an Enum.");

                _enumType = value;
            }
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var enumValues = _enumValues ?? Enum.GetValues(EnumType);

            return (
              from object enumValue in enumValues
              select new EnumerationMember
              {
                  Value = enumValue,
                  Description = GetDescription(enumValue)
              }).ToArray();
        }

        private string GetDescription(object enumValue)
        {
            var descriptionAttribute = EnumType
              .GetField(enumValue.ToString())
              .GetCustomAttributes(typeof(DescriptionAttribute), false)
              .FirstOrDefault() as DescriptionAttribute;


            return descriptionAttribute != null
              ? descriptionAttribute.Description
              : enumValue.ToString();
        }

        public class EnumerationMember
        {
            public string Description { get; set; }
            public object Value { get; set; }
        }
    }

But when I try something like this

ItemsSource="{Binding Source={my:Enumeration Statuses}}" 

I get NullReferenceException somewhere in InitializeComponent

   at MS.Internal.Xaml.Runtime.ClrObjectRuntime.GetConverterInstance[TConverterBase](XamlValueConverter`1 converter)
   at MS.Internal.Xaml.Runtime.ClrObjectRuntime.CreateObjectWithTypeConverter(ServiceProviderContext serviceContext, XamlValueConverter`1 ts, Object value)
   at MS.Internal.Xaml.Runtime.ClrObjectRuntime.CreateFromValue(ServiceProviderContext serviceContext, XamlValueConverter`1 ts, Object value, XamlMember property)
   at MS.Internal.Xaml.Runtime.PartialTrustTolerantRuntime.CreateFromValue(ServiceProviderContext serviceContext, XamlValueConverter`1 ts, Object value, XamlMember property)
   at System.Xaml.XamlObjectWriter.Logic_CreateFromValue(ObjectWriterContext ctx, XamlValueConverter`1 typeConverter, Object value, XamlMember property, String targetName, IAddLineInfo lineInfo)
   at System.Xaml.XamlObjectWriter.Logic_ConvertPositionalParamsToArgs(ObjectWriterContext ctx)
   at System.Xaml.XamlObjectWriter.WriteEndMember()
   at System.Xaml.XamlWriter.WriteNode(XamlReader reader)
   at System.Windows.Markup.WpfXamlLoader.TransformNodes(XamlReader xamlReader, XamlObjectWriter xamlWriter, Boolean onlyLoadOneNode, Boolean skipJournaledProperties, Boolean shouldPassLineNumberInfo, IXamlLineInfo xamlLineInfo, IXamlLineInfoConsumer xamlLineInfoConsumer, XamlContextStack`1 stack, IStyleConnector styleConnector)
   at System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri)
   at System.Windows.Markup.WpfXamlLoader.LoadBaml(XamlReader xamlReader, Boolean skipJournaledProperties, Object rootObject, XamlAccessLevel accessLevel, Uri baseUri)
   at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream)
   at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
   at MvvmLight3.MainWindow.InitializeComponent() in \MainWindow.xaml:line 1
   at MvvmLight3.MainWindow..ctor() in \MainWindow.xaml.cs:line 16
Community
  • 1
  • 1
Alex P.
  • 3,697
  • 9
  • 45
  • 110

2 Answers2

2

Instead of using MarkupExtension you may create a converter that gets an enum value and returns its description. Then use this converter when binding.

public class EnumToDescriptionConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null)
        {
            return String.Empty;
        }

        Type type = value.GetType();

        return
            type.IsEnum
                ? GetDescription(type, value)
                : String.Empty;
    }

    private string GetDescription(Type enumType, object enumValue)
    {
        var descriptionAttribute =
            enumType
                .GetField(enumValue.ToString())
                .GetCustomAttributes(typeof(DescriptionAttribute), false)
                .FirstOrDefault() as DescriptionAttribute;

        return
            descriptionAttribute != null
                ? descriptionAttribute.Description
                : enumValue.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

After building the project you can use it in XAML when your namespace is added as xml namespace:

<Window [...] xmlns:local="clr-namespace:YourNameSpace">

Add this converter to the window (or to the app) resources:

<Window.Resources>
    <local:EnumToDescriptionConverter x:Key="EnumToDescriptionConverter" />
</Window.Resources>

Then you can use it when binding the Statuses to a ListBox.

<ListBox ItemsSource="{Binding Statuses}" SelectedItem="{Binding CurrentStatus}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource EnumToDescriptionConverter}}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

CurrentStatus and Statuses are properties of the viewmodel:

public class MainViewModel
{
    public Status CurrentStatus { get; set; }

    public IEnumerable<Status> Statuses
    {
        get
        {
            return new Status[]
            {
                Status.Available,
                Status.Away
            };
        }
    }
}
Gabor
  • 3,021
  • 1
  • 11
  • 20
  • Yes, I know about this approach, that is what I did. I was trying to use `MarkupExtension ` because it looked more clean than Converter + ItemTemplate. But apparently it is not suitable for this task. – Alex P. Apr 24 '16 at 18:19
1

Statuses isn't anything here:

ItemsSource="{Binding Source={my:Enumeration Statuses}}" 

my:Enumeration isn't a binding. What's supposed to be providing a value to Enumeration's constructor? You couldn't put a binding there anyway: Notice that the exception is getting thrown while the XAML is being loaded. Your viewmodel won't be around for some time yet. You could programatically create a binding in ProvideValue, I guess, but... what would that add to the mix here? You already have a markup extension that creates bindings: It's called Binding.

I don't actually even understand why you need the MarkupExtension in that case: You don't need it to enumerate anything, since what you're handing it is an enumeration already.

I may be missing something, but won't this do what you want?

ItemsSource="{Binding Statuses}" 

The advantage of an enum-enumerating MarkupExtension is that you just give it a type, and it saves you the trouble of making the list yourself, or writing the code for a property that returns Enum.GetValues(typeof(MyEnumType)).Cast<MyEnumType>();. But you're already making the list yourself.