Thursday, May 5, 2011

Expose a collection of enums (flags) in Visual Studio designer

I've got an enum of types of data that might be shown in a .NET Forms control and I want to provide an interface for consumers of the control to filter some of the types (set some of the flags). A bit field seems the logical way to do this, unfortunately, the enum starts at 0 rather than 1 (0, 1, 2, 4, 8, ...) and can't be changed.

How can I expose this set of flags so that it can be easily configured programmatically or through the Visual Studio designer?

From stackoverflow
  • You would need to write a UITypeEditor to do the work, and associate it with the property via [EditorAttribute].

    edit now with example - a fairly long one, I'm afraid - but most of the code can be shared between types, fortunately.

    You can't use a single composite enum value because of the zero - so here I'm using a HashSet<T> to hold the selected enums - fairly easy to re-work to List<T> if you have .NET 2.0/3.0, though.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing.Design;
    using System.Text;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
    public class MyControl : UserControl
    {
        public MyControl()
        {
            Values = new HashSet<MyEnum>();
        }
        [Editor(typeof(MyEnumSetEditor), typeof(UITypeEditor))]
        [TypeConverter(typeof(MyEnumSetConverter))]
        public HashSet<MyEnum> Values { get; set; }
    }
    
    public enum MyEnum
    {  // numbers as per the question...
        A = 0, B = 1, C = 2, D = 4, E = 8
    }
    class MyEnumSetEditor : EnumSetEditor<MyEnum> { }
    class MyEnumSetConverter : EnumSetConverter<MyEnum> { }
    
    // from here down is shared between types
    abstract class EnumSetConverter<T> : TypeConverter where T : struct
    {
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
        }
        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if(destinationType == typeof(string))
            {
                HashSet<T> set = (HashSet<T>)value;
                if (set == null) return "(null)";
    
                StringBuilder sb = new StringBuilder();
                foreach (T item in Enum.GetValues(typeof(T)))
                {
                    if (set.Contains(item))
                    {
                        if (sb.Length > 0) sb.Append(", ");
                        sb.Append(item);
                    }
                }
                return sb.ToString();
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
    
    public abstract class EnumSetEditor<T> : UITypeEditor where T : struct
    {
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.DropDown;
        }
        public override bool IsDropDownResizable
        {
            get { return true; }
        }
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            IWindowsFormsEditorService svc = (IWindowsFormsEditorService)
                provider.GetService(typeof(IWindowsFormsEditorService));
            HashSet<T> set = value as HashSet<T>;
            if (svc != null && set != null)
            {
                UserControl ctrl = new UserControl();
                CheckedListBox clb = new CheckedListBox();
                clb.Dock = DockStyle.Fill;
                Button btn = new Button();
                btn.Dock = DockStyle.Bottom;
                foreach (T item in Enum.GetValues(typeof(T)))
                {
                    clb.Items.Add(item, set.Contains(item));
                }
                ctrl.Controls.Add(clb);
                ctrl.Controls.Add(btn);
                btn.Text = "OK";
                btn.Click += delegate
                {
                    set.Clear();
                    foreach (T item in clb.CheckedItems)
                    {
                        set.Add(item);
                    }
                    svc.CloseDropDown();
                };
                svc.DropDownControl(ctrl);
            }
    
            return value;
        }
    }
    
    dmo : Wow. Marc - I really appreciate you taking the time to do this.

0 comments:

Post a Comment