In the last couple of weeks I've been learning PropertyGrid. I need to display the properties of a series of objects of various classes, but they are all derived from class Ctrl. For instance, there are:
- Ctrl_BUTTON
- Ctrl_SQLLISTVIEW
- Ctrl_TEXTBOX
- nine classes in all
The derived classes contain the additional properties that are not in the base class and only apply to some of the derived classes. Each property can either be of a specific type of an Expression (a class that allows the user to enter a string that will later evaluate to a canonical value). In the PropertyGrid, I've implemented a dropdown by writing class ExpressionPropertyEditor which inherits UITypeEditor. I've currently only implemented the Ctrl_TEXTBOX.ReadOnly property which can be a bool, or an Expression, and it provides a dropdown that looks like this before the user has entered an expression:
or something like this if they have:
When the user clicks on the Expression entry, an expression editor is opened. Currently the relevant parts of my Ctrl_TEXTBOX class look like this:
public class Ctrl_TEXTBOX : Ctrl
{
private object _readOnly = false;
[DescriptionAttribute("Whether the user can change the contents.")]
[Editor(typeof(ExpressionPropertyEditor), typeof(UITypeEditor))]
public object ReadOnly
{
get
{
return _readOnly;
}
set
{
try
{
_readOnly = ExpressionHelper.BoolExp2Object(value);
}
catch (Exception ex)
{
base.Globals.Errs.Raise(ex);
throw ex;
}
}
}
}
For reference ExpressionHelper contains this, simply:
public static class ExpressionHelper
{
public static object BoolExp2Object(object oValue)
{
try
{
switch (Helper.GetClassNameFromObject(oValue).ToLower())
{
case "expression": return oValue;
case "string":
switch (((string)oValue).ToLower())
{
case "true": return true;
case "false": return false;
default: throw new NotImplementedException();
}
case "boolean":
case "bool": return oValue;
default: throw new NotImplementedException();
}
}
catch (Exception ex)
{
throw ex;
}
}
}
My implementation of ExpressionPropertyEditor looks like this:
public class ExpressionPropertyEditor : UITypeEditor
{
private IWindowsFormsEditorService _editorService;
private ListBox _listBox;
private Ctrl _ctrl;
public ExpressionPropertyEditor()
{
}
// Displays the UI for value selection.
public override object EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
// coded with help from: https://stackoverflow.com/questions/5171037/display-list-of-custom-objects-as-a-drop-down-in-the-propertiesgrid
Ctrl oCtrl;
Ctrl_TEXTBOX oTextBox;
Expression oExp = null;
frmExpressionEditor2 frm;
bool bOk = false;
_editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
_listBox = new ListBox();
_listBox.SelectionMode = SelectionMode.One;
_listBox.SelectedValueChanged += EventHandler_ListBox_SelectedValueChanged;
_listBox.Items.Add(true);
_listBox.Items.Add(false);
switch (Helper.GetClassNameFromObject(context.Instance))
{
case "Ctrl_TEXTBOX":
oTextBox = (Ctrl_TEXTBOX)context.Instance;
switch (Helper.GetClassNameFromObject(oTextBox.ReadOnly))
{
case "Boolean":
case "bool":
// cos we need a way to make an expression
oExp = new Expression(oTextBox.Globals, "BOOLEAN", enumExpressionSource.Expression, Consts.EXPRESSION_PROPERTY_NAME);
_listBox.Items.Add(oExp);
break;
case "Expression":
oExp = (Expression)oTextBox.ReadOnly;
_listBox.Items.Add(oExp);
break;
default:
// this shouldn't happen really; just wrap as an expression
oExp = new Expression(oTextBox.Globals, "BOOLEAN", enumExpressionSource.Expression, Consts.EXPRESSION_PROPERTY_NAME);
_listBox.Items.Add(oExp);
break;
}
break;
}
_ctrl = (Ctrl)context.Instance;
_editorService.DropDownControl(_listBox); // this will return when EventHandler_ListBox_SelectedValueChanged calls editorService.CloseDropDown, like a modal dialog.
if (_listBox.SelectedItem == null) // no selection, return the passed-in value as is
return value;
if (Helper.GetClassNameFromObject(_listBox.SelectedItem) == "Expression")
{
frm = new frmExpressionEditor2();
if (!frm.EditExpression(_ctrl.Globals, _ctrl.Server, _ctrl.App, _ctrl.Frm, ref oExp, ref bOk)) throw new Exception("Could not open expression editor.");
}
return _listBox.SelectedItem;
}
private void EventHandler_ListBox_SelectedValueChanged(object sender, EventArgs e)
{
_editorService.CloseDropDown();
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
public override bool IsDropDownResizable
{
get { return true; }
}
}
My question is: how do I generalize ExpressionPropertyEditor so that I can use it on any of the derived classes of Ctrl, for any of the boolean properties that I want to include Expressions with? Currently it is locked to Ctrl_TEXTBOX.ReadOnly. If this is not possible to do, I have to created dozens of classes containing identical logic with just the names changed - not good for code reuse.