I am displaying the properties of an object in a PropertyGrid. One of those properties is an enum. So it is displayed with a combobox editor that lists all of the values of the enum. That's all great, but I need to filter that list of enum values at runtime. I can't just decorate some of the enum values with the Browsable attribute, because which of the values I want to hide will vary. At the moment I'm leaning towards a custom UITypeEditor, but I thought I should check with the smart people first.
A TypeConverter
may be all you need, especially if the legal subset "changes from moment to moment". Given an enum:
public enum AnimalSpecies
{
Canine, Feline, Rodent, Dragon, Unicorn, Robot
}
...which is used on a property:
public class Animal
{
...
[TypeConverter(typeof(AnimalSpeciesConverter))]
public AnimalSpecies Species { get; set; }
If you decorate the enum, the converter will apply to anything/everything which uses it; on the property, it affects only that. For the converter, you will want to override GetStandardValues()
:
class AnimalSpeciesConverter : TypeConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
Animal test = context.Instance as Animal;
if (test != null)
return true;
else
return base.GetStandardValuesSupported();
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
string[] names = Enum.GetNames(typeof(AnimalSpecies))
.Where(x => !x.StartsWith("Rob")).ToArray();
return new StandardValuesCollection(names);
}
}
GetStandardValuesSupported()
returns true to indicate the TypeConverter
can and will supply the values. GetStandardValuesExclusive()
indicates that, no the user can't opt to type in their own value. GetStandardValues()
filters the list and returns the new subset. Result:
If the filtering logic is more complex, you might want to let the class instance determine the contents. Or perhaps you'd just prefer that logic to remain in the class. To do that I like to use an interface:
public interface IValuesProvider
{
string[] GetValues();
}
public class Animal : IValuesProvider
{
public string Name { get; set; }
[TypeConverter(typeof(AnimalSpeciesConverter))]
public AnimalSpecies Species { get; set; }
public int Value { get; set; }
...
public string[] GetValues()
{
List<string> names = Enum.GetNames(typeof(AnimalSpecies)).ToList();
// your logic here
if (Value < 10)
names.Remove(AnimalSpecies.Feline.ToString());
else if (Value < 50)
names.Remove(AnimalSpecies.Robot.ToString());
return names.ToArray();
}
Now, when the PropertyGrid asks for the values, we can get them from the instance:
class AnimalSpeciesConverter : TypeConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
IValuesProvider test = context.Instance as IValuesProvider;
if (test != null)
return true;
else
return base.GetStandardValuesSupported();
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
IValuesProvider item = context.Instance as IValuesProvider;
return new StandardValuesCollection(item.GetValues());
}
}
As an alternative, you can skip the interface and make the GetValues
method private if you dont like exposing it that wayand get the values by Reflection. In that case, I might return true for GetStandardValuesSupported()
only if the method exists. That way you don't ever have a case where you have returned true for Get...Supported()
and then not have a way to supply them.
This is dynamic: the values list is refreshed/repolled every time the drop down opens. Each time you change the Value
, the drop down list is updated each time it opens.