3

I have a WPF combobox that is bound to an enum like this:

<Window.Resources>
    <local:EnumDescriptionConverter x:Key="enumDescriptionConverter"/>
    <ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="cityNamesDataProvider">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="local:MyModel+CityNamesEnum"/>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

<ComboBox x:Name="cityNameComboBox" ItemsSource="{Binding Source={StaticResource cityNamesDataProvider}}" SelectionChanged="cityNameComboBox_SelectionChanged">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource enumDescriptionConverter}}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

The enum that I'm binding to has description attributes and looks like this:

public enum CityNamesEnum
{
    [Description("New York City")]
    NewYorkCity,
    [Description("Chicago")]
    Chicago,
    [Description("Los Angeles")]
    LosAngeles
}

I don't always want to display each enum value. Is it possible to toggle the visibility of one or more of the enum values? If these were ComboBoxItems, I think I could simply set the .Visibility property to hidden but since they're enum values, I'm not sure if this is possible. Does anyone know?

bmt22033
  • 6,880
  • 14
  • 69
  • 98

2 Answers2

8

Why not just create a normal C# method which does the filtering for you and then have the ObjectDataProvider point to that method instead

static method IEnumerable<CityNamesEnum> MyFilter() {
  yield return CityNames.NewYorkCity;
  yield return CityNames.Chicago;
}

XAML

<ObjectDataProvider 
   MethodName="MyFilter" 
   ObjectType="{x:Type local:TheType}" 
   x:Key="cityNamesDataProvider">
</ObjectDataProvider>
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • thanks, that's a good suggestion. I actually need to filter the enum values for this particular combobox based on the selected item in another combobox. I'll see how I can work your idea into that solution. – bmt22033 Jan 08 '14 at 14:22
0

The example here is applied to a ComboBox, but will work all the same for any Enum Binding. It will however do exactly what you want: Hide Enum values that don't have a Description. This also provides an easy Binding method. (as well as sorting them by Index though, can be easily changed in the function "SortEnumValuesByIndex()")

Origin:

This anwser is based on the original work of Brian Lagunas' EnumBindingSourceExtension + EnumDescriptionTypeConverter. I have made modifications for it to better suit my needs.

What I changed:

  1. Extended the Enum class with a boolean function that checks if the EnumValue has the [Description] attribute or not

    public static bool HasDescriptionAttribute(this Enum value)
    {
        var attribute = value.GetType().GetField(value.ToString())
                            .GetCustomAttributes(typeof(DescriptionAttribute), false)
                            .FirstOrDefault();                                
    
        return (attribute != null);
    }
    
  2. Modified Brian's "ConvertTo()" function in "EnumDescriptionTypeConverter" to return "null" in case the [Description] attribute was not applied

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        ...
        // Original: return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : value.ToString();
        return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : null;
    }
    
  3. Modified Brian's "ProvideValue()" function in "EnumBindingSourceExtension" by editing it's return value by calling my own function

    ProvideValue(IServiceProvider serviceProvider)
    {
        ...
        // Original: return enumValues
        return SortEnumValuesByIndex(enumValues);
    
        ...
        // Original: return tempArray
        return SortEnumValuesByIndex(tempArray);
    }
    

    And adding my function to Sort the enum by Index (Original code had problems when going across Projects ..), and Strip out any Values that don't have the [Description] attribute:

    private object SortEnumValuesByIndex(Array enumValues)
    {
        var values = enumValues.Cast<Enum>().ToList();
        var indexed = new Dictionary<int, Enum>();
        foreach (var value in values)
        {
            int index = (int)Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()));
            indexed.Add(index, value);
        }
    
        return indexed.OrderBy(x => x.Key).Select(x => x.Value).Where(x => x.HasDescriptionAttribute()).Cast<Enum>();
    }     
    

This example has been applied to ComboBoxes:

Note: Failures in Uploading images to the Server, so i added a URL to the images in question

<ComboBox x:Name="ConversionPreset_ComboBox" Grid.Row="4" Grid.Column="1" Margin="5,5,5,5" ItemsSource="{objects:EnumBindingSource EnumType={x:Type enums:ConversionPreset}}" SelectedIndex="2" SelectionChanged="ConversionPreset_ComboBox_SelectionChanged" />
<ComboBox x:Name="OutputType_ComboBox" Grid.Row="4" Grid.Column="2" Margin="5,5,5,5" ItemsSource="{objects:EnumBindingSource EnumType={x:Type enums:Output}}" SelectedIndex="1" SelectionChanged="OutputType_ComboBox_SelectionChanged" />

With code behind:

    private Enumeration.Output Output { get; set; }

    private void OutputType_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        => Output = (Enumeration.Output)OutputType_ComboBox.SelectedItem;

    private Enumeration.ConversionPreset ConversionPreset { get; set; }

    private void ConversionPreset_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        => ConversionPreset = (Enumeration.ConversionPreset)ConversionPreset_ComboBox.SelectedItem;

The "ConversionPreset" Enum and a Picture of the ComboBox Rendered

[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum ConversionPreset
{
    [Description("Very Slow (Smaller File Size)")]
    VerySlow = -2,

    [Description("Slow (Smaller File Size)")]
    Slow = -1,

    [Description("Medium (Balanced File Size)")]
    Medium = 0,

    [Description("Fast (Bigger File Size)")]
    Fast = 1,

    [Description("Very Fast (Bigger File Size)")]
    VeryFast = 2,

    [Description("Ultra Fast (Biggest File Size)")]
    UltraFast = 3
}

The "Output" Enum and a Picture of the ComboBox Rendered

[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum Output
{
    // This will be hidden in the Output
    None = -1,

    [Description("Video")]
    Video = 0,

    [Description("Audio")]
    Audio = 1
}


Update

The code above is intended to Bind Enums and Manage their Output without the need for Code Behind to manually fill the ComboBoxes. I just now read that you want to approach it from in Code, having more control over the ComboBox Content. This should get you on your way just fine. Just create a class and paste this content into it:

using System;
using System.ComponentModel;

namespace Attributes
{
    public class Hidden : Attribute { }

    public class StartingDescription : DescriptionAttribute
    {
        public StartingDescription(string description) : base(description) { }
    }

    public class FinishedDescription : DescriptionAttribute
    {
        public FinishedDescription(string description) : base(description) { }
    }
}

namespace Enumerations
{
    using Attributes;

    public enum Order
    {
        None = -1,
        [Description("Get"),
         StartingDescription("Getting"),
         FinishedDescription("Got")]
        Get = 0,
        [Description("Initialize"),
         StartingDescription("Initializing"),
         FinishedDescription("Initialized")]
        Initialize = 1,
        [Description("Download"),
         StartingDescription("Downloading"),
         FinishedDescription("Downloaded")]
        Download = 2
    }

    public enum Output
    {
        [Hidden]
        None = -1,

        [Description("Video")]
        Video = 33,

        [Description("Audio")]
        Audio = 44
    }
}

namespace Classes
{
    using System.Linq;

    public static class Extensions
    {
        public static string Format(this string value, params object?[] args)
            => String.Format(value, args);

        public static bool HasAttribute(this Enum enumValue, Type attributeType)
        {
            var result = enumValue.GetType().GetField(enumValue.ToString())
                            .GetCustomAttributes(attributeType, false)
                            .FirstOrDefault();

            return (result != null);
        }

        public static int GetIndex(this Enum enumValue)
            => (int)Convert.ChangeType(enumValue, Enum.GetUnderlyingType(enumValue.GetType()));

        public static string GetDescription(this Enum enumValue, Type attributetype = null)
        {
            if (attributetype == null)
                attributetype = typeof(DescriptionAttribute);

            var field = enumValue.GetType().GetField(enumValue.ToString());
            var attributes = Attribute.GetCustomAttributes(field, false).Where(x => x.GetType().Equals(attributetype)).Cast<DescriptionAttribute>();
            var value = attributes.FirstOrDefault();
            if (value != null)
                return value.Description;

            return enumValue.ToString();
        }
    }    
}

namespace ExecutionNamespace
{
    using System.Collections.Generic;
    using System.Linq;

    using Classes;
    using Attributes;
    using System.Diagnostics;

    internal class Example
    {
        public static SortedList<int, string> GetEnumByIndex(Type enumType, Type outputType = null)
        {
            if (enumType == null)
                throw new ArgumentNullException("enumType was not Definied");

            var enumValues = Enum.GetValues(enumType).Cast<Enum>().ToList();
            if ((enumValues == null) || (enumValues.Count == 0))
                throw new ArgumentNullException("Could not find any Enumeration Values");

            if (outputType == null)
                outputType = typeof(DescriptionAttribute);
        
            var indexed = new SortedList<int, string>();
            foreach (var value in enumValues)
                if (!value.HasAttribute(typeof(Hidden)))
                    indexed.Add(value.GetIndex(), value.GetDescription(outputType));
            return indexed;
        }

        public static SortedList<string, string> GetEnumByValue(Type enumType, Type outputType = null)
        {
            if (enumType == null)
                throw new ArgumentNullException("enumType was not Definied");

            var enumValues = Enum.GetValues(enumType).Cast<Enum>().ToList();
            if ((enumValues == null) || (enumValues.Count == 0))
                throw new ArgumentNullException("Could not find any Enumeration Values");

            if (outputType == null)
                outputType = typeof(DescriptionAttribute);

            var indexed = new SortedList<string, string>();
            foreach (var value in enumValues)
                if (!value.HasAttribute(typeof(Hidden)))
                    indexed.Add(value.ToString(), value.GetDescription(outputType));
            return indexed;
        }

        public static void Run()
        {
            Type type = null;


            type = typeof(Enumerations.Order);
            Debug.WriteLine("{0} by Index".Format(type.ToString()));
            foreach (var valuePair in GetEnumByIndex(type, typeof(StartingDescription)))
                Debug.WriteLine("Index:{1} & Description:{2} ".Format(type.ToString(), valuePair.Key, valuePair.Value));
            Debug.WriteLine("");

            type = typeof(Enumerations.Order);
            Debug.WriteLine("{0} by Value".Format(type.ToString()));
            foreach (var valuePair in GetEnumByValue(type, typeof(StartingDescription)))
                Debug.WriteLine("Value:{1} & Description:{2} ".Format(type.ToString(), valuePair.Key, valuePair.Value));
            Debug.WriteLine("");



            type = typeof(Enumerations.Output);
            Debug.WriteLine("{0} by Index".Format(type.ToString()));
            foreach (var valuePair in GetEnumByIndex(type))
                Debug.WriteLine("Index:{1} & Description:{2} ".Format(type.ToString(), valuePair.Key, valuePair.Value));
            Debug.WriteLine("");

            type = typeof(Enumerations.Output);
            Debug.WriteLine("{0} by Value".Format(type.ToString()));
            foreach (var valuePair in GetEnumByValue(type))
                Debug.WriteLine("Value:{1} & Description:{2} ".Format(type.ToString(), valuePair.Key, valuePair.Value));
            Debug.WriteLine("");
        }
    }
}

Then just do this on like a Button Click, and watch the Output Window:

private void Button_Click(object sender, RoutedEventArgs e)
{
    ExecutionNamespace.Example.Run();            
}

It will bring you this output:

Enumerations.Order by Index
Index:-1 & Description:None
Index:0 & Description:Getting
Index:1 & Description:Initializing
Index:2 & Description:Downloading

Enumerations.Order by Value
Value:Download & Description:Downloading
Value:Get & Description:Getting
Value:Initialize & Description:Initializing
Value:None & Description:None

Enumerations.Output by Index
Index:33 & Description:Video
Index:44 & Description:Audio

Enumerations.Output by Value
Value:Audio & Description:Audio
Value:Video & Description:Video