【问题标题】:C# PropertyGrid UITypeEditor Agnostic to the object and propertyC# PropertyGrid UITypeEditor 与对象和属性无关
【发布时间】:2021-04-13 09:07:15
【问题描述】:

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

  • Ctrl_BUTTON
  • Ctrl_SQLLISTVIEW
  • Ctrl_TEXTBOX
  • 一共九类

派生类包含基类中没有的附加属性,并且仅适用于某些派生类。每个属性都可以是特定类型的表达式(允许用户输入稍后将评估为规范值的字符串的类)。在 PropertyGrid 中,我通过编写继承 UITypeEditor 的类 ExpressionPropertyEditor 实现了一个下拉菜单。我目前只实现了可以是布尔值或表达式的 Ctrl_TEXTBOX.ReadOnly 属性,它在用户输入表达式之前提供了一个如下所示的下拉列表:

或类似的东西,如果他们有:

当用户单击表达式条目时,将打开一个表达式编辑器。目前我的 Ctrl_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;
                }
            }
        }
    }

供参考 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: 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; }
        }

    }

我的问题是:我如何概括 ExpressionPropertyEditor 以便我可以在 Ctrl 的任何派生类上使用它,用于我想要包含 Expressions 的任何布尔属性?目前它被锁定为 Ctrl_TEXTBOX.ReadOnly。如果无法做到这一点,我必须创建几十个包含相同逻辑的类,只更改名称 - 不利于代码重用。

【问题讨论】:

  • context.Instance 和 context.PropertyDescriptor 的组合应该足以知道正在编辑什么类中的什么属性。接下来,由派生类覆盖的 Ctrl 上的虚拟方法可以帮助执行特定于类+属性的任务。此外,您可以将编辑器声明为 Ctrl 的嵌套类,以访问私有成员和受保护成员。
  • 如果你想让他们继续回答,你应该回答他们。
  • 很抱歉西蒙,我会试着跳到你的时间表。我目前正在考虑做出 Rubidium 友好建议的一些更改,并且会在我有,嗯,对反馈有用的东西时进行反馈。
  • @Rubidium 37,非常感谢。我现在有一个通用方法,它适用于 Ctrl 子类上的任何布尔值。花了我一段时间,因为我不太熟悉反射方法,我必须使用这些方法来获取值,而不必将 context.Instance 紧密绑定到特定类型。我还没有将它嵌套在 Ctrl 中(可能并不真的需要)。我将在下面发布修改后的代码。再次感谢。

标签: c# generics propertygrid


【解决方案1】:

感谢上面@Rubidium 37 的 cmets,我修改了 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;
            }
        }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-15
    • 1970-01-01
    • 1970-01-01
    • 2011-08-11
    • 1970-01-01
    • 1970-01-01
    • 2014-03-10
    • 1970-01-01
    相关资源
    最近更新 更多