How to Have an Enum Bound Combobox with Custom String Formatting for Enum Values

How do I have an enum bound combobox with custom string formatting for enum values?

You could write an TypeConverter that reads specified attributes to look them up in your resources. Thus you would get multi-language support for display names without much hassle.

Look into the TypeConverter's ConvertFrom/ConvertTo methods, and use reflection to read attributes on your enum fields.

Bind Combobox with Enum Description

Try this:

cbTipos.DisplayMember = "Description";
cbTipos.ValueMember = "Value";
cbTipos.DataSource = Enum.GetValues(typeof(TiposTrabajo))
.Cast<Enum>()
.Select(value => new
{
(Attribute.GetCustomAttribute(value.GetType().GetField(value.ToString()), typeof(DescriptionAttribute)) as DescriptionAttribute).Description,
value
})
.OrderBy(item => item.value)
.ToList();

In order for this to work, all the values must have a description or you'll get a NullReference Exception. Hope that helps.

How do I bind an enum to a ComboBox and hide a certain value in C#

The GetValues method will return all of these constants as an array. Instead create a custom list.

ThisComboBOx.ItemsSource = new List<BlueOrRed> {BlueOrRed.Blue, BlueOrRed.Red};

If you do not want to create the list yourself, you can also exclude the None constant using Linq.

ThisComboBOx.ItemsSource = ((BlueOrRed[]) Enum.GetValues(typeof(BlueOrRed))).Except(new[] { BlueOrRed.None });

Friendly-Format Enum for use in ComboBoxes, CheckedListBoxes, etc

The basis of my solution is a struct that can wrap Enum values and override ToString().

Enter the EnumWrapper:

public struct EnumWrapper
{
private readonly Enum e;
public EnumWrapper(Enum e) {
this.e = e;
}
public static implicit operator Enum(EnumWrapper wrapper) {
return wrapper.e;
}
public static explicit operator EnumWrapper(Enum e) {
return new EnumWrapper(e);
}
public override string ToString() {
return e.ToStringFriendly();
}
}

The method ToStringFriendly() is defined as an extension method on Enum:

using System.Text.RegularExpressions;
public static class _Extensions
{
public static string ToStringFriendly(this Enum e)
{
string s = e.ToString();

// enforce a charset: letters, numbers, and underscores
s = Regex.Replace(s, "[^A-Za-z0-9_]", "");

// separate numbers from letters
s = Regex.Replace(s, "([a-zA-Z])([0-9])", "$1 $2");

// separate letters from numbers
s = Regex.Replace(s, "([0-9])([a-zA-Z])", "$1 $2");

// space lowercases before uppercase (word boundary)
s = Regex.Replace(s, "([a-z])([A-Z])", "$1 $2");

// see that the nice pretty capitalized words are spaced left
s = Regex.Replace(s, "(?!^)([^ _])([A-Z][a-z]+)", "$1 $2");

// replace double underscores with colon-space delimiter
s = Regex.Replace(s, "__", ": ");

// finally replace single underscores with hyphens
s = Regex.Replace(s, "_", "-");

return s;
}
}

Now, to add any Enum value to a ComboBox, for example,

comboBox.Items.Add((EnumWrapper)MyEnum.SomeValue);

And to get it back out (after null-testing, of course):

MyEnum myValue = (MyEnum)(Enum)(EnumWrapper)comboBox.SelectedItem;

And that's it. Now what are the pros and cons of this approach?

The Pros:

  • You can pass the enum values (almost) directly in and out of your controls. Just cast back and forth to/from EnumWrapper.
  • This works nicely for all Pascal-cased Enum values with a few special cases. One example might be a value called MyValue__DescriptionOf123_ENUMValue which would come out of ToStringFriendly() as "My Value: Description of 123-ENUM Value".
  • The mechanics (a struct and an extension method) can be written once and tucked out of the way. There is no additional coding in each Enum. This means it works nicely as above for enums you didn't write, assuming Pascal case, which in .NET is a good assumption. Note: This is why I'd say this answer, however elegant, is not a better solution than mine, as it requires that you can add a TypeConverter attribute to each Enum.

The Cons:

  • This doesn't support custom descriptions or multiple languages. For example a value like Encoding.EBCDIC will always come through as "EBCDIC" and does not allow you to manually type "Extended Binary Coded Decimal Interchange Code", much less other languages.

Future Work

One could add custom descriptions and multi-language support by changing ToStringFriendly() to do a language-lookup for that value before de-Pascal-casing.

For more Regex fun, see this thread.

ComboBox bound to an enum type's values while also having a blank entry?

(Please see my edit to the question where I clarified that I don't want to bind to a collection of strings).

After more fiddling, the following monstrosity seems to work. combo1.SelectedItem is of type object and will either be a DBNull or a (boxed?) enum value. Is this code advisable?

combo1.DataSource = (new object[] { DBNull.Value }
.Concat(Enum.GetValues(refToAnEnumType)
.Cast<object>())).ToList()

Edit: I see Adam and Andrew's methods could easily be adapted to do the same thing. Thanks guys!

Custom define enum names for combobox

use the DescriptionAttribute and an appropriate extension method to read it out.

public enum BaudRate
{
[Description("115200 kb")]
BR115200 = 7,
[Description("19200 kb")]
BR19200 = 4,
[Description("230400 kb")]
BR230400 = 8,
[Description("2400 kb")]
BR2400 = 1,
[Description("115200 kb")]
BR38400 = 5,
[Description("4800 kb")]
BR4800 = 2,
[Description("57600 kb")]
BR57600 = 6,
[Description("9600 kb")]
BR9600 = 3
}

The extension method:

public static class EnumExtension
{
/// <summary>
/// Gets the string of an DescriptionAttribute of an Enum.
/// </summary>
/// <param name="value">The Enum value for which the description is needed.</param>
/// <returns>If a DescriptionAttribute is set it return the content of it.
/// Otherwise just the raw name as string.</returns>
public static string Description(this Enum value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}

string description = value.ToString();
FieldInfo fieldInfo = value.GetType().GetField(description);
DescriptionAttribute[] attributes =
(DescriptionAttribute[])
fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

if (attributes != null && attributes.Length > 0)
{
description = attributes[0].Description;
}

return description;
}

/// <summary>
/// Creates an List with all keys and values of a given Enum class
/// </summary>
/// <typeparam name="T">Must be derived from class Enum!</typeparam>
/// <returns>A list of KeyValuePair<Enum, string> with all available
/// names and values of the given Enum.</returns>
public static IList<KeyValuePair<Enum, string>> ToList<T>() where T : struct
{
var type = typeof(T);

if (!type.IsEnum)
{
throw new ArgumentException("T must be an enum");
}

return (IList<KeyValuePair<Enum, string>>)
Enum.GetValues(type)
.OfType<Enum>()
.Select(e => new KeyValuePair<Enum, string>(e, e.Description()))
.ToArray();
}

public static T GetValueFromDescription<T>(string description) where T : struct
{
var type = typeof(T);

if(!type.IsEnum)
{
throw new ArgumentException("T must be an enum");
}

foreach(var field in type.GetFields())
{
var attribute = Attribute.GetCustomAttribute(field,
typeof(DescriptionAttribute)) as DescriptionAttribute;

if(attribute != null)
{
if(attribute.Description == description)
{
return (T)field.GetValue(null);
}
}
else
{
if(field.Name == description)
{
return (T)field.GetValue(null);
}
}
}

throw new ArgumentOutOfRangeException("description");
// or return default(T);
}
}

At the and you can simply apply this to your combo box by calling:

var list = EnumExtension.ToList<BaudRate>();
myComboBox.DataSource = list;
myComboBox.ValueMember = "Key";
myComboBox.DisplayMember = "Value";

Setting ComboBox DataSource to an enum and binding SelectedValue

Option 1

To use SelectedValue for data binding, Create a calss DataItem:

public class DataItem
{
public MyEnum Value { get; set; }
public string Text { get; set; }
}

Then use this code for data binding:

this.comboBox1.DataSource = Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>()
.Select(x => new DataItem() { Value = x, Text = x.ToString() })
.ToList();
this.comboBox1.ValueMember = "Value";
this.comboBox1.DisplayMember = "Text";

this.comboBox1.DataBindings.Add(
new Binding("SelectedValue", yourObjectToBind, "PropertyOfYourObject"));

You can make generic DataItem<T> containing public T Value { get; set; } to make it more reusable in your project.

Option 2

To Use SelectedItem for data binding:

this.comboBox1.DataSource = Enum.GetValues(typeof(MyEnum));
this.comboBox1.DataBindings.Add(
new Binding("SelectedItem", yourObjectToBind, "PropertyOfYourObject"));

Option 3

To use SelectedValue for data binding as another option suggested by Ivan Stoev, you can perform data to binding this way:

this.comboBox1.DataSource = Enum.GetValues(typeof(MyEnum));
this.comboBox1.DataBindings.Add(
new Binding("SelectedValue", yourObjectToBind, "PropertyOfYourObject",
true, DataSourceUpdateMode.OnPropertyChanged));

Edit by Jannik:

The basic generic approach of option 1 would look like this:

public class ComboBoxItemWrapper<T>
{
public T Value { get; set; }
public string Text { get; set; }
}


Related Topics



Leave a reply



Submit