1

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

Community
  • 1
  • 1
Asım Gündüz
  • 1,257
  • 3
  • 17
  • 42

1 Answers1

3

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);
    }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Just as a side note, keep in mind, if your goal is just selecting one of properties of `T`, there are easier options as well. But I posted the answer and full working example for learning purpose containing some useful tricks including, usage of `context`, creating an instance of a dynamic type using reflection, getting properties to show in a combobox and so on :) – Reza Aghaei Oct 29 '17 at 07:22
  • Note `context` is (in the context of the property grid edition) simply an instance of `GridItem` wich is a public class: https://msdn.microsoft.com/en-us/library/system.windows.forms.griditem.aspx. Even without this, the `ITypeDescriptorContext` has an `Instance` property wich is the component instance. No need to use reflection. – Simon Mourier Oct 29 '17 at 07:32
  • @SimonMourier Thanks for the comment. About the Instance property, I'm aware of that property and I've used it [here for this answer](https://stackoverflow.com/a/46133257/3110834). Probably when I was writing the example, I changed my mind because of a mistake in my code. I changed the code to use `Instnace` property again. About the type, yes, the type that you mentioned is the base for the type that I used and I had used it just because of `Component` property. There is no point in using it anymore. Thanks for the comment :) – Reza Aghaei Oct 29 '17 at 08:06
  • @RezaAghaei worked like a charm=) I have one more question: When I open the BdEditor Form I make multiple inputs into a list. and I return the list, what I would like to do is to pass that list into BdEditor so that if the list is not empty I can add more items o remove from it. How do i do this? is this possible? – Asım Gündüz Nov 02 '17 at 13:44
  • Have you took a look at my answer [here](https://stackoverflow.com/a/46133257/3110834)? I guess this is what you are looking for. – Reza Aghaei Nov 02 '17 at 13:52
  • @RezaAghaei yes I did, My problem here is that if I have a string propery in my MyBaseForm and MyEditorForm I'm able to pass value from BaseForm to Editor Form, inside the using statement what I do is f.GetType().GetProperty(propName).SetValue(f, propValue, null); so by doing this I'm able to pass the string value. however if it is a list I receive an error saying "object of type 'system.object' cannot be converted to type 'system.collections.genericlis'1[system.string]'. why is this happening? – Asım Gündüz Nov 06 '17 at 19:58
  • :\ Maybe I'm not getting exactly what you are asking. I guess it would be better if you ask a question containing code that shows what you are trying to do. I also will take a look at your new question containing [MCVE]. – Reza Aghaei Nov 06 '17 at 20:13