C# PropertyGrid UITypeEditor 与对象无关并且 属性
C# PropertyGrid UITypeEditor Agnostic to the object and property
在过去的几周里,我一直在学习 PropertyGrid。我需要显示各种classes的一系列对象的属性,但它们都是从class Ctrl导出的。例如有:
- Ctrl_BUTTON
- Ctrl_SQLLISTVIEW
- Ctrl_TEXTBOX
- 共九个class
派生的 classes 包含基本 class 中没有的附加属性,并且仅适用于某些派生的 classes。每个 属性 可以是特定类型的表达式(一个 class 允许用户输入一个字符串,该字符串稍后将评估为规范值)。在 PropertyGrid 中,我通过编写继承 UITypeEditor 的 class ExpressionPropertyEditor 实现了下拉列表。我目前只实现了 Ctrl_TEXTBOX.ReadOnly 属性 ,它可以是布尔值或表达式,它提供了一个下拉列表,在用户输入表达式之前看起来像这样:
或类似的东西,如果他们有:
当用户单击表达式条目时,将打开一个表达式编辑器。目前我的 Ctrl_TEXTBOX class 的相关部分如下所示:
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;
}
}
}
}
作为参考,ExpressionHelper 包含这个,简单地说:
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;
}
}
}
我的 ExpressionPropertyEditor 实现如下所示:
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:
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; }
}
}
我的问题是:如何概括 ExpressionPropertyEditor,以便我可以在 Ctrl 的任何派生 classes 上使用它,用于我想包含表达式的任何布尔属性?目前它被锁定为 Ctrl_TEXTBOX.ReadOnly。如果无法做到这一点,我必须创建数十个 classes,其中包含相同的逻辑,只是更改了名称 - 不利于代码重用。
感谢 @Rubidium 37 上面的评论,我已经修改了 EditValue 方法,这样它就不需要紧紧绑定到 Ctrl 的子class 并且可以使用任何 属性 名称.然后,我在 System.Reflection 中使用 PropertyInfo class 来获取 属性 的当前值,而无需对 属性 名称进行硬编码。
刚刚发布了更新的 EditValue 方法。
public override object EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
// coded with help from:
Ctrl oCtrl = null;
Expression oExp = null;
frmExpressionEditor2 frm = null;
bool bOk = false;
System.Reflection.PropertyInfo oPropInfo = null;
string cPropertyName = "";
object oCurrentValue = null;
try
{
oCtrl = (Ctrl)context.Instance;
_editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
// setup a listbox with the possible values the boolean can take, true, false, and an expression
_listBox = new ListBox();
_listBox.SelectionMode = SelectionMode.One;
_listBox.SelectedValueChanged += EventHandler_ListBox_SelectedValueChanged;
//_listBox.DrawItem += EventHandler_ListBox_DrawItem; // do this if you have loads of time and want a pretty fx next to the Expression
_listBox.Items.Add(true);
_listBox.Items.Add(false);
// we either want to show Consts.EXPRESSION_PROPERTY_NAME, or if the value of the property is currently an expression, show that instead
cPropertyName = context.PropertyDescriptor.Name;
oPropInfo = context.Instance.GetType().GetProperty(cPropertyName);
oCurrentValue = oPropInfo.GetValue(context.Instance, null); // this returns the current value of the property
switch (Helper.GetClassNameFromObject(oCurrentValue))
{
case "Boolean":
case "bool": // just show the default expression string
oExp = new Expression(oCtrl.Globals, "BOOLEAN", enumExpressionSource.Expression, Consts.EXPRESSION_PROPERTY_NAME);
_listBox.Items.Add(oExp);
break;
case "Expression": // show the current value of the boolean expression
oExp = (Expression)oCurrentValue;
_listBox.Items.Add(oExp);
break;
default: // this shouldn't happen, so reset to default expression string
oExp = new Expression(oCtrl.Globals, "BOOLEAN", enumExpressionSource.Expression, Consts.EXPRESSION_PROPERTY_NAME);
_listBox.Items.Add(oExp);
break;
}
// show the list
_editorService.DropDownControl(_listBox); // this will return when EventHandler_ListBox_SelectedValueChanged calls editorService.CloseDropDown, like a modal dialog.
// no selection, return the passed-in value as is
if (_listBox.SelectedItem == null) return value;
// and if necessary, allow the user to edit the expression
if (Helper.GetClassNameFromObject(_listBox.SelectedItem) == "Expression")
{
frm = new frmExpressionEditor2();
if (!frm.EditExpression(oCtrl.Globals, oCtrl.Server, oCtrl.App, oCtrl.Frm, ref oExp, ref bOk)) throw new Exception("Could not open expression editor.");
return oExp;
}
return _listBox.SelectedItem;
}
catch (Exception ex)
{
oCtrl.Globals.Errs.Raise(ex);
return null;
}
}
在过去的几周里,我一直在学习 PropertyGrid。我需要显示各种classes的一系列对象的属性,但它们都是从class Ctrl导出的。例如有:
- Ctrl_BUTTON
- Ctrl_SQLLISTVIEW
- Ctrl_TEXTBOX
- 共九个class
派生的 classes 包含基本 class 中没有的附加属性,并且仅适用于某些派生的 classes。每个 属性 可以是特定类型的表达式(一个 class 允许用户输入一个字符串,该字符串稍后将评估为规范值)。在 PropertyGrid 中,我通过编写继承 UITypeEditor 的 class ExpressionPropertyEditor 实现了下拉列表。我目前只实现了 Ctrl_TEXTBOX.ReadOnly 属性 ,它可以是布尔值或表达式,它提供了一个下拉列表,在用户输入表达式之前看起来像这样:
或类似的东西,如果他们有:
当用户单击表达式条目时,将打开一个表达式编辑器。目前我的 Ctrl_TEXTBOX class 的相关部分如下所示:
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;
}
}
}
}
作为参考,ExpressionHelper 包含这个,简单地说:
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;
}
}
}
我的 ExpressionPropertyEditor 实现如下所示:
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:
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; }
}
}
我的问题是:如何概括 ExpressionPropertyEditor,以便我可以在 Ctrl 的任何派生 classes 上使用它,用于我想包含表达式的任何布尔属性?目前它被锁定为 Ctrl_TEXTBOX.ReadOnly。如果无法做到这一点,我必须创建数十个 classes,其中包含相同的逻辑,只是更改了名称 - 不利于代码重用。
感谢 @Rubidium 37 上面的评论,我已经修改了 EditValue 方法,这样它就不需要紧紧绑定到 Ctrl 的子class 并且可以使用任何 属性 名称.然后,我在 System.Reflection 中使用 PropertyInfo class 来获取 属性 的当前值,而无需对 属性 名称进行硬编码。
刚刚发布了更新的 EditValue 方法。
public override object EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
// coded with help from:
Ctrl oCtrl = null;
Expression oExp = null;
frmExpressionEditor2 frm = null;
bool bOk = false;
System.Reflection.PropertyInfo oPropInfo = null;
string cPropertyName = "";
object oCurrentValue = null;
try
{
oCtrl = (Ctrl)context.Instance;
_editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
// setup a listbox with the possible values the boolean can take, true, false, and an expression
_listBox = new ListBox();
_listBox.SelectionMode = SelectionMode.One;
_listBox.SelectedValueChanged += EventHandler_ListBox_SelectedValueChanged;
//_listBox.DrawItem += EventHandler_ListBox_DrawItem; // do this if you have loads of time and want a pretty fx next to the Expression
_listBox.Items.Add(true);
_listBox.Items.Add(false);
// we either want to show Consts.EXPRESSION_PROPERTY_NAME, or if the value of the property is currently an expression, show that instead
cPropertyName = context.PropertyDescriptor.Name;
oPropInfo = context.Instance.GetType().GetProperty(cPropertyName);
oCurrentValue = oPropInfo.GetValue(context.Instance, null); // this returns the current value of the property
switch (Helper.GetClassNameFromObject(oCurrentValue))
{
case "Boolean":
case "bool": // just show the default expression string
oExp = new Expression(oCtrl.Globals, "BOOLEAN", enumExpressionSource.Expression, Consts.EXPRESSION_PROPERTY_NAME);
_listBox.Items.Add(oExp);
break;
case "Expression": // show the current value of the boolean expression
oExp = (Expression)oCurrentValue;
_listBox.Items.Add(oExp);
break;
default: // this shouldn't happen, so reset to default expression string
oExp = new Expression(oCtrl.Globals, "BOOLEAN", enumExpressionSource.Expression, Consts.EXPRESSION_PROPERTY_NAME);
_listBox.Items.Add(oExp);
break;
}
// show the list
_editorService.DropDownControl(_listBox); // this will return when EventHandler_ListBox_SelectedValueChanged calls editorService.CloseDropDown, like a modal dialog.
// no selection, return the passed-in value as is
if (_listBox.SelectedItem == null) return value;
// and if necessary, allow the user to edit the expression
if (Helper.GetClassNameFromObject(_listBox.SelectedItem) == "Expression")
{
frm = new frmExpressionEditor2();
if (!frm.EditExpression(oCtrl.Globals, oCtrl.Server, oCtrl.App, oCtrl.Frm, ref oExp, ref bOk)) throw new Exception("Could not open expression editor.");
return oExp;
}
return _listBox.SelectedItem;
}
catch (Exception ex)
{
oCtrl.Globals.Errs.Raise(ex);
return null;
}
}