Search code examples
c#winformsgenericspropertygriduitypeeditor

How to create custom generic PropertyGrid editor item which opens a form?


I have a custom generic form

 public partial class MyTestForm1 : MyBrowseForm<CONTACTS_BASE>

where CONTACTS_BASE is an EntityFramework entity.

on the parent class I would like to have a property so that when I click on it at designer time from the property grid it opens a form and on that form I would like to have a combobox populated with the fields on the CONTACTS_BASE entity.

I have found this post Marc Gravell's answer helped me to open a new form when clicked on the property at design time and I have also populated the ComboBox with fields of CONTACTS_BASE. but to do this on the form load event I called to function I made to that returns the list of fields and set it to ComboBox's DataSource.

comboBox1.DataSource = EntityBase.BaseGetTableFieldList2<CONTACTS_BASE>();

enter image description here

however what I would like to accomplish is making this generic

so what I would like to do is something like this to populate the ComboBox.

public partial class BdEditorForm <TParentEntity>:Form where TParentEntity:class
{
    private void BdEditorForm_Load(object sender, EventArgs e)
    {       
     comboBox1.DataSource = EntityBase.BaseGetTableFieldList2<TParentEntity>();
    }
}

is something like this possible? because When I try to do this I need to make make TypeEditor generic too and then when giving attributes to the property I create

[Editor(typeof(BdFormTypeEditor<TParentEntity>), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]

and it says: enter image description here

any help is appreciated thanks and sorry for my bad english


Solution

  • Short Answer

    To know how to solve the problem, you need to know EditValue method has a context parameter which is of type ITypeDescriptorContext and has an Instance property which is the owner object of the property that you are editing. Having the owner (the Form) we know the type of the form and therefore we know the generic parameter type and therefore we can create our generic editor form.

    Step By Step Example

    Above fact is the key point for the answer, but to solve the problem, you need to apply some other tricks as well. For example you should get a generic type and create an instance of it using reflection.

    Here I put a project containing the whole source code of the example:

    Here are steps of the example which creates a custom model UI Type Editor to show a list of properties of T when you are editing a specific property of a form which is derived from MyBaseForm<T>.

    Generic Base Form

    It's the base form for other forms which contains SomeProperty, the property which you want to edit using a custom editor.

    Add a MyGenericType property to the class which returns typeof(T), the generic type of the form:

    public partial class MyBaseForm<T> : Form
    {
        public MyBaseForm()
        {
            InitializeComponent();
        }
    
        [Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
        public string SomeProperty { get; set; }
    
        [Browsable(false)]
        public Type MyGenericType { get { return typeof(T); } }
    }
    

    Derived Form

    It's a sample derived form which is derived from the MyBaseForm<T>. We will edit SomeProperty of an instance of this class.

    public partial class MyDerivedForm : MyBaseForm<MySampleModel>
    {
        public MyDerivedForm()
        {
            InitializeComponent();
        }
    }
    

    Sample Model

    It's a sample model which we are going to show its properties in the custom editor window.

    public class MySampleModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Price { get; set; }
    }
    

    Editor Form

    It's the form which UITypeEditor will show. In the form, we fill comoBox1 with field names of the generic argument.

    public partial class MyEditorForm<T> : Form
    {
        public MyEditorForm()
        {
            InitializeComponent();
            this.StartPosition = FormStartPosition.CenterScreen;
            var list = ListBindingHelper.GetListItemProperties(typeof(T))
                .Cast<PropertyDescriptor>()
                .Select(x => new { Text = x.Name, Value = x }).ToList();
            this.comboBox1.DataSource = list;
            this.comboBox1.DisplayMember = "Text";
            this.comboBox1.ValueMember = "Value";
        }
        public string SelectedProperty
        {
            get
            {
                return comboBox1.GetItemText(comboBox1.SelectedItem);
            }
        }
        private void button1_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.OK;
        }
    }
    

    UI Type Editor

    When calling EditValue method of the UITypeEditor, the context parameter is of type System.Windows.Forms.PropertyGridInternal.PropertyDescriptorGridEntry which has a Component property which its value is the instance of the form which you are editing, so we know the type of the form and therefore we know the generic parameter type and therefore we can create our generic editor form and use it.

    public class MyUITypeEditor : UITypeEditor
    {
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.Modal;
        }
        public override object EditValue(ITypeDescriptorContext context,
            IServiceProvider provider, object value)
        {
            var svc = provider.GetService(typeof(IWindowsFormsEditorService))
                as IWindowsFormsEditorService;
            var myGenericTypeProperty = context.Instance.GetType()
                .GetProperty("MyGenericType");
            var genericArgument = (Type)myGenericTypeProperty.GetValue(context.Instance);
            var editorFormType = typeof(MyEditorForm<>);
            var genericArguments = new[] { genericArgument };
            var editorFormInstance = editorFormType.MakeGenericType(genericArguments);
            if (svc != null)
            {
                using (var f = (Form)Activator.CreateInstance(editorFormInstance))
                    if (svc.ShowDialog(f) == DialogResult.OK)
                        return ((dynamic)f).SelectedProperty;
            }
            else
            {
                using (var f = (Form)Activator.CreateInstance(editorFormInstance))
                    if (f.ShowDialog() == DialogResult.OK)
                        return ((dynamic)f).SelectedProperty;
            }
            return base.EditValue(context, provider, value);
        }
    }