Reputation: 1
I'm trying to show a ComboBox on a PropertyGrid. I don't mean a dropdown string list because I need the index of the selected item associated with the description. This is easy to obtain with a ComboBox control on a Form but I can't get the same result by publishing a ComboBox type property in a PropertyGrid.
On My code below I'm trying to get a ComboBox on a PropertyGrid.
public class Wrap {
private ComboBox _Combo = new ComboBox();
public Wrap() {
_Combo.SelectedIndexChanged += new System.EventHandler(Combo_SelectedIndexChanged);
_Combo.Items.Add(new Item() { Text = "Text1", Value = "Value1" });
_Combo.Items.Add(new Item() { Text = "Text2", Value = "Value2" });
_Combo.DisplayMember = "Text";
_Combo.ValueMember = "Value";
_Combo.SelectedIndex = 1;
}
private void Combo_SelectedIndexChanged(object sender, EventArgs e) {
ComboBox cb = sender as ComboBox;
int i = cb.SelectedIndex;
if (i < 0)
return;
Item it = cb.Items[i] as Item;
string s = it.Value;
}
public ComboBox Combo {
get { return _Combo; }
set {
// Temporary test to avoid "null" (the only selection possible from propertygrid)
if (value == null)
return;
_Combo = value;
}
}
}
public class Item {
public string Value { set; get; }
public string Text { set; get; }
}
Upvotes: 0
Views: 194
Reputation: 18023
Assuming this is a WinForms PropertyGrid
you don't need to explicitly use a ComboBox
control: a custom TypeConverter
handles everything for you.
Please also note that the DisplayMember
/ValueMember
distinction for a ComboBox
is typically needed only when you need to store some primitive underlying value (eg. in a database) to represent some more high level instance in your actual model but in a PropertyGrid
you don't need that. You can directly use your high level custom type in the grid. Still, you can optionally parse the low-level "value member" if you want, as it is demonstrated in the following example.
You didn't provide an example so I made up one. Note the [TypeConverter(...)]
attribute above the property or the type itself
public class MyClassToEditInAGrid
{
// some regular properties here
public string StringProp { get; set; }
public int IntProp { get; set; }
// [...]
// My special property with the 'ComboBox'.
// You can omit the TypeConverter here if it is defined globally for the type
[TypeConverter(typeof(ItemConverter))]
[Description("Either select an item or type the corresponding text or underlying value")]
public Item MySelectableProperty { get; set; }
}
Please note that you can define the type converter for your Item
"globally" as well:
// You can define the type converter also here so it is used by default for Item.
// Or, you can indicate it just in MyClassToEditInAGrid for the property as above.
[TypeConverter(typeof(ItemConverter))]
public class Item
{
public string Value { set; get; }
public string Text { set; get; }
public override string ToString() => Text;
}
public class ItemConverter : TypeConverter
{
// The selectable items
private static readonly Item[] _items =
{
new() { Text = "Text1", Value = "Value1" },
new() { Text = "Text2", Value = "Value2" },
new() { Text = "Text3", Value = "Value3" },
new() { Text = "Text4", Value = "Value4" },
};
// If your "ValueMember" is not a string, add its type as well
// (eg. int, some custom enum, etc.)
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) => destinationType == typeof(string);
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => sourceType == typeof(string);
// Destination type is always string in a PropertyGrid but if your "ValueMember"
// is some different type you might want to add it, too
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (destinationType != typeof(string) || value is not Item item)
return base.ConvertTo(context, culture, value, destinationType);
return item.Text;
}
// You might want to parse from string and the type of your "ValueMember".
// Both are strings in your example.
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is not string str)
return base.ConvertFrom(context, culture, value);
// 1. Parsing by text, case-insensitive
Item? result = _items.FirstOrDefault(i => String.Equals(i.Text, str, StringComparison.OrdinalIgnoreCase));
// 2. Parsing by value, case-sensitive
result ??= _items.FirstOrDefault(i => i.Value == str);
return result ?? throw new ArgumentException($"Invalid value: {str}", nameof(value));
}
// This enables "ComboBox" for the property
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
// This tells that it's not a simple read-only drop down
// but you can also type values to parse
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => false;
// This returns the items to display in the drop-down area
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext? context) => new(_items);
}
Place a PropertyGrid
into a Form
and use it like this:
public partial class CustomTypeConverterDemo : Form
{
public CustomTypeConverterDemo()
{
InitializeComponent();
var myObj = new MyClassToEditInAGrid();
propertyGrid1.SelectedObject = myObj;
}
}
The result, demonstrating item selection and parsing from typed text or value:
Upvotes: 1