C#PropertyGrid UITypeEditor与对象和属性无关

C#PropertyGrid UITypeEditor与对象和属性无关,c#,generics,propertygrid,C#,Generics,Propertygrid,在过去的几周里,我一直在学习PropertyGrid。我需要显示不同类的一系列对象的属性,但它们都是从类Ctrl派生的。例如,有: Ctrl_按钮 Ctrl\u SQLLISTVIEW Ctrl\u文本框 总共九节课 派生类包含不在基类中且仅适用于某些派生类的附加属性。每个属性都可以是特定类型的表达式(一个允许用户输入字符串的类,该字符串稍后将计算为规范值)。在PropertyGrid中,我通过编写继承UITypeEditor的类ExpressionPropertyEdit实现了一个下拉列表

在过去的几周里,我一直在学习PropertyGrid。我需要显示不同类的一系列对象的属性,但它们都是从类Ctrl派生的。例如,有:

  • Ctrl_按钮
  • Ctrl\u SQLLISTVIEW
  • Ctrl\u文本框
  • 总共九节课
派生类包含不在基类中且仅适用于某些派生类的附加属性。每个属性都可以是特定类型的表达式(一个允许用户输入字符串的类,该字符串稍后将计算为规范值)。在PropertyGrid中,我通过编写继承UITypeEditor的类ExpressionPropertyEdit实现了一个下拉列表。我目前只实现了Ctrl_TEXTBOX.ReadOnly属性,它可以是bool,也可以是表达式,在用户输入表达式之前,它提供了如下下拉列表:

如果他们有:

当用户单击表达式条目时,将打开表达式编辑器。当前,我的Ctrl\u TEXTBOX类的相关部分如下所示:

    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;
                }
            }
        }
    }
    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; }
        }

    }
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 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;
                }
            }
        }
    }
    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; }
        }

    }

我的问题是:如何对ExpressionPropertyEdit进行泛化,以便在Ctrl的任何派生类上使用它,以获得我想要包含表达式的任何布尔属性?当前它被锁定为Ctrl\u TEXTBOX.ReadOnly。如果这是不可能的,我必须创建几十个包含相同逻辑的类,只更改名称-这不利于代码重用。

多亏了@Rubidium 37上面的注释,我修改了EditValue方法,这样它就不需要紧密绑定到Ctrl的子类,并且可以使用任何属性名称。然后,我在System.Reflection中使用PropertyInfo类来获取属性的当前值,而不必硬编码属性名称

只发布更新的EditValue方法

       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 = 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;
            }
        }

context.Instance和context.PropertyDescriptor的组合应该足以知道正在编辑的类中的属性。接下来,Ctrl上的虚拟方法(由派生类重写)可以帮助执行类+属性特定任务。此外,您可以将编辑器声明为Ctrl的嵌套类,以访问私有和受保护的成员。如果希望其他人继续回答,您应该回答他们。非常抱歉,Simon,我将尝试跳转并跳到您的时间表。我目前正在考虑对Rubidium提出的建议进行一些修改,当我得到一些有用的反馈时,我会予以反馈。@Rubidium 37,非常感谢。我现在有了一个通用方法,它可以处理Ctrl子类上的任何布尔值。我花了一些时间,因为我不太熟悉反射方法,我必须使用反射方法来获取值,而不必将context.Instance紧密绑定到特定类型。我还没有将它嵌套在Ctrl中(可能真的不需要)。我将在下面发布修改后的代码。再次感谢。