The .NET framework is huge, but not so huge that it does everything for everyone; there are things that they in Redmond miss or don’t do for whatever reason but is still generally applicable to many developers. So, dear reader, I present to you a series of posts on stuff I find missing in .NET, typically where even the Google fails to find the answer. It could be a useful class, a technique, a good practice or documentation that should be in the framework but isn’t or isn’t widely publicized. Click here for a complete list of Missing .NET entries.
I’m sure, Dear Reader, if you wait long enough, I’ll do all the exercises I leave to you in my posts. I wrote last time about displaying enums in WPF using databinding. I left it as an exercise to do this in Windows Forms and ASP.NET. Well, it turns out that I needed a Windows Forms version for more than just a programming exercise. So I figured I’d share something like it with all 5 of you regular readers.
In case you don’t want to click through to the WPF article, let me recap it here for you since we’ll be using some of the code from that article. I’m ambivalent towards enums; I often think they are less useful in many situations in which they are used than a custom class. I do, however, have little choice when it comes to using other libraries or .NET Framework code which may use them. One of the troublesome areas of enums is showing them in UI without going insane writing the same code over and over again with only the slight difference of the enum type to distinguish each version. Last time I used the first-rate data-binding in WPF to show enums in a WPF ComboBox. Then I handled the case where you don’t own the enum. I also dealt with the issues of internationalization and human readable text instead of the names of each enum value. In this article, I’ll use the somewhat less first-rate data-binding in Windows Forms to do the same thing.
The first thing we need is a way to mark our enum with the text we want in place of the value name. The perfect way to do that, as I said last time, is to use attributes. The class I presented last time is independent of UI frameworks, so I’ll use it again here. It’s reproduced below:
using System; namespace MissingNet.ComponentModel { [AttributeUsage(AttributeTargets.Field)] public sealed class DisplayStringAttribute : Attribute { private readonly string value; public string Value { get { return value; } } public string ResourceKey { get; set; } public DisplayStringAttribute(string value) { this.value = value; } public DisplayStringAttribute() { } } }
We can apply this to the values of our custom enum, like so:
public enum MyEnum { [DisplayString("My Value")] Value, [DisplayString("This means On")] On, [DisplayString("Off means not On")] Off, [DisplayString("The great unknown")] Unknown, [DisplayString("I ain't got none")] None }
Doesn’t look as pretty as an unadorned enum, but commercial quality code often looks less attractive than you’re typical code snippet on MSDN. If we want to internationalize our app, we can have different display strings for different languages by setting the ResourceKey property instead of the value constructor:
public enum MyEnum { [DisplayString(ResourceKey="MyEnum_Value")] Value, [DisplayString(ResourceKey="MyEnum_On")] On, [DisplayString(ResourceKey="MyEnum_Off")] Off, [DisplayString(ResourceKey="MyEnum_Unknown")] Unknown, [DisplayString(ResourceKey="MyEnum_None")] None }
Don’t forget to add the strings to your resx file for each language!
Now that we’ve reviewed how to associate the display strings with the enum values, we need a way to get that data into the UI. For that I’ll be using one of the unsung heroes of the .NET 2.0 WinForms improvements: BindingSource.
Earlier I called the data-binding in WinForms less than first-rate. It’s only in comparison to WPF, which isn’t really fair: WPF was designed from the ground up with data-binding as a feature. By contrast, Windows Forms is an elegant OO wrapper around the flat Win32 API, an API that is ancient in computer years. It’s so old they didn’t even have data when it was designed. So there is nothing as elegant as, say, DataContext in Windows Forms, but it’s still world’s better than the alternative of doing it all yourself.
They faked it pretty good in .NET 1.1 for simple databinding (simple in the Windows Forms data binding sense of just hooking up to a property); complex databinding (binding to a list or collection or DataSet) was a bit more onerous if you modified the collection independent of the Form that was displaying it. In .NET 2.0, they improved things tremendously by following the old adage: You can at least partially solve any problem in computer science with one more level of indirection. [The full quote, attributed to David Wheeler is, “Any problem in computer science can be solved with another layer of indirection. But that usually will create another problem.”]
And that level of indirection takes the form of the BindingSource. To describe the BindingSource, let me quote directly from the excellent book on the subject of Windows Forms databinding, Data Binding with Windows Forms 2.0, by Brian Noyes:
The BindingSource component solves a number of tricky problems that surfaced with the approach of directly binding data sources to controls in .NET 1.X. It provides a layer of indirection between a data source and bound controls that makes a number of things easier. Additionally, it surfaces important control points and access to the underlying data source in a way that saves you from having to delve into the data-binding mechanisms of a form the way you had to in the past. A binding source also gives you a single API to program against from the form’s perspective, and lets a lot of your form code remain decoupled from the specific data source type. This prevents you from having to adapt your programmatic coding patterns to each different type of data that your application works with… [p111-112]
Sounds perfect for our needs, doesn’t it? Displaying enums, from a Windows Forms data-binding perspective, is actually fairly easy: it’s a read-only data source with read-only items, so there is no need to deal with adding, removing or editing. In fact the only tough part is filling the “list” with the entries. We’ve already done something similar with in WPF.
Here’s the EnumBindingSource class:
public class EnumBindingSource : BindingSource { private readonly object dataSource; private readonly PropertyInfo property; public EnumBindingSource(object dataSource, string propertyName) { this.dataSource = dataSource; this.property = dataSource.GetType().GetProperty(propertyName); Type enumType = property.PropertyType; FieldInfo[] fields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static); List<NameEnumPair> source = new List<NameEnumPair>(fields.Length); foreach (FieldInfo f in fields) { DisplayStringAttribute[] a = (DisplayStringAttribute[])f.GetCustomAttributes(typeof(DisplayStringAttribute), false); string displayString = GetDisplayStringValue(a, enumType); object enumValue = f.GetValue(null); NameEnumPair pair = new NameEnumPair(displayString, enumValue); source.Add(pair); } int index = source.FindIndex(value => value.Value.Equals(property.GetValue(dataSource, null))); this.DataSource = source; this.Position = index; } public override bool AllowEdit { get { return false; } } public override bool AllowNew { get { return false; } set { throw new NotSupportedException();} } public override bool AllowRemove { get { return false; } } protected override void OnCurrentChanged(EventArgs e) { base.OnCurrentChanged(e); //set the value NameEnumPair value = (NameEnumPair)this.Current; property.SetValue(dataSource, value.Value, null); } private static string GetDisplayStringValue(DisplayStringAttribute[] a, Type type) { if (a == null || a.Length == 0) return null; DisplayStringAttribute dsa = a[0]; if (!string.IsNullOrEmpty(dsa.ResourceKey)) { ResourceManager rm = new ResourceManager(type); return rm.GetString(dsa.ResourceKey); } return dsa.Value; } private class NameEnumPair { private readonly string displayName; private readonly object value; public NameEnumPair(string displayName, object value) { this.displayName = displayName; this.value = value; } public string DisplayName { [DebuggerStepThrough] get { return displayName; } } public object Value { [DebuggerStepThrough] get { return value; } } public override string ToString() { return DisplayName; } } }
All the work happens in the constructor: we use reflection to get the type of the property passed in, get the enum values in the type, read the DisplayStringAttribute values, add them to a list, set the DataSource property, and we’re done. I’ve overridden the AllowXxx properties to make this an immutable BindingSource; there are more properties to override to lock this down as a framework type, but this is a good start. If I were really hardcore, I’d implement all the interfaces that BindingSource does myself, but I don’t see the value.
So, this works for enums that you own, but like I said in the WPF article, that is really rare. What you need is a way to declare the strings for enums that you don’t own. Again, using the WPF implmentation as a reference, we merely need to create a class that will contain the data and set it appropriately. But here, we don’t have the requirement to support XAML, so we have a bit more freedom. Since this is pretty much an immutable type, setting it in the constructor is the way to go. I just need an overloaded constructor. We also need a type to hold the data we want to set. We already have that type with the nested class NameEnumPair, we just need to make it public, and probably move it to the outer scope, as most .NET types are wont to do.
Here’re the new constructors:
public EnumBindingSource(object dataSource, string propertyName) : this(dataSource, propertyName, null) { } public EnumBindingSource(object dataSource, string propertyName, IEnumerable<NameEnumPair> overriddenDisplayValues) { this.dataSource = dataSource; this.property = dataSource.GetType().GetProperty(propertyName); List<NameEnumPair> source; if (overriddenDisplayValues == null) { Type enumType = property.PropertyType; FieldInfo[] fields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static); source = new List<NameEnumPair>(fields.Length); foreach (FieldInfo f in fields) { DisplayStringAttribute[] a = (DisplayStringAttribute[])f.GetCustomAttributes(typeof(DisplayStringAttribute), false); string displayString = GetDisplayStringValue(a, enumType); object enumValue = f.GetValue(null); NameEnumPair pair = new NameEnumPair(displayString, enumValue); source.Add(pair); } } else { source = new List<NameEnumPair>(overriddenDisplayValues); } int index = source.FindIndex(value => value.Value.Equals(property.GetValue(dataSource, null))); this.DataSource = source; this.Position = index; }
Now we have a way to display enums in ComboBoxes in Windows Forms. Client code looks like the following:
public class MyCustomClass { public MyEnum MyEnumProperty { get; set; } public MyCustomClass() { MyEnumProperty = MyEnum.On; } } public partial class Form1 : Form { public Form1() { InitializeComponent(); MyCustomClass myCustomObject = new MyCustomClass(); this.comboBox1.DropDownStyle = ComboBoxStyle.DropDownList; this.comboBox1.DataSource = new EnumBindingSource(myCustomObject, "MyEnumProperty"); } }