Friday, January 30, 2009
Clean PropertyGrid selectors
Over the past week, I've been working alot with the PropertyGrid control. These controls are great if you have a bunch of wildly-differing or unknown classes. You can feed the class to the PropertyGrid and it allows the user to directly manipulate the object.
The drawback, many times the field names or values are not user-friendly. In fact... they can be completely ugly.
The key is being sure that your classes are PropertyGrid-friendly. Each field should specify some compile-time attributes like the ones below:
However, what happens when you want to edit something that's not a primitive like double or string?
I had several fields that could take a string as input, but would parse the string and result in a specific object. I've found a very nice, clean, simple solution for this. Before I'd stumbled on this solution, I was using
First, I would have to identify all of the properties that I want to mangle with this UITypeEditor.
I would create a UITypeEditor that relied on a corresponding form (which was nothing more than a form containing a listbox at 100% width and height.)
I would then have to include a form MyDropdownForm:
This was alot of code. the worst part is, if I wanted to change anything about how it worked, I needed to add two new files to my project. And they were clunky files, at that.
That is... until I discovered TypeConverter. This simplified things INCREDIBLY. I still had to identify the properties, but the Attribute was much more concise:
The rest just became very easy.
This is MUCH simpler, faster, easier, and more maintainable. I hope this little review helps someone else to build the best PropertyGrid-enabled objects!
The drawback, many times the field names or values are not user-friendly. In fact... they can be completely ugly.
The key is being sure that your classes are PropertyGrid-friendly. Each field should specify some compile-time attributes like the ones below:
[
CategoryAttribute("2 - Bottom"),
DisplayName("Bottom Diameter"),
Description("The diameter of the bottom attachment.")
]
public virtual double BottomDiameter {
get
{
...
However, what happens when you want to edit something that's not a primitive like double or string?
I had several fields that could take a string as input, but would parse the string and result in a specific object. I've found a very nice, clean, simple solution for this. Before I'd stumbled on this solution, I was using
UITypeEditor. First, I would have to identify all of the properties that I want to mangle with this UITypeEditor.
[
EditorAttribute(typeof(ObjectSelector), typeof(UITypeEditor)),
DisplayName("The Object List")
]
public SomeObject O
{
// ... get, set ...
}
I would create a UITypeEditor that relied on a corresponding form (which was nothing more than a form containing a listbox at 100% width and height.)
class ObjectSelector: UITypeEditor
{
IWindowsFormsEditorService editorService = null;
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
// ...
// Define some variables...
// ...
if (provider != null)
{
editorService =
provider.GetService(
typeof(IWindowsFormsEditorService))
as IWindowsFormsEditorService;
}
if (editorService != null)
{
MyDropdownForm f =
new MyDropdownForm(
(SomeObjectThing)value,
editorService);
// Populate the listbox on the form before displaying it.
f.Populate();
editorService.DropDownControl(f);
value = f.SelectedObject;
if (value == null) return null;
// if the two objects are actually the same object, then return.
if (value.Equals(OldValue) || f. SelectedObject == OldValue)
{
return value;
} else {
// ...
// Populate my new object as-needed.
// ...
}
}
return value;
}
}
I would then have to include a form MyDropdownForm:
public partial class MyDropdownForm : UserControl
{
private IWindowsFormsEditorService editorService = null;
public object SelectedObject = null;
public MyDropdownForm (
object CurrentValue,
IWindowsFormsEditorService EdService)
{
InitializeComponent();
editorService = EdService;
SelectedObject = CurrentValue;
ObjectList.SelectedIndexChanged += new EventHandler(ObjectList_SelectedIndexChanged);
}
void ObjectList_SelectedIndexChanged(object sender, EventArgs e)
{
SelectedSObject = SegmentList.SelectedItem;
this.editorService.CloseDropDown();
}
internal void Populate()
{
// ... you get the idea...
}
}
This was alot of code. the worst part is, if I wanted to change anything about how it worked, I needed to add two new files to my project. And they were clunky files, at that.
That is... until I discovered TypeConverter. This simplified things INCREDIBLY. I still had to identify the properties, but the Attribute was much more concise:
[
TypeConverter(typeof(ObjectConverter)),
DisplayName("The Object List")
]
// NOTE that it is a string. Use the private in the get and set to
// use the object's StringParse(str) method.
public string O
{
// ... get, set ...
}
private SomeObject _o = new SomeObject();
The rest just became very easy.
internal class SegmentConverter : StringConverter
{
public override bool GetStandardValuesSupported
(ITypeDescriptorContext context)
{
//True - Show the Combobox
//False - Just take/set the string.
return true;
}
public override bool GetStandardValuesExclusive
(ITypeDescriptorContext context)
{
//False - Editable
//True - ReadOnly
return false;
}
public override StandardValuesCollection GetStandardValues
(ITypeDescriptorContext context)
{
// Do whatever you have to do here to get your object into a list
// or array of strings. Luckily, I already had one of those methods
// in a static Data-Access Object.
ListobjNames = daoObject.GetNames();
return new StandardValuesCollection(segNames.ToArray());
}
}
This is MUCH simpler, faster, easier, and more maintainable. I hope this little review helps someone else to build the best PropertyGrid-enabled objects!
Subscribe to Posts [Atom]
Post a Comment