How to Create Custom Propertygrid Editor Item Which Opens a Form

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

You need to implement a modal UITypeEditor, using the IWindowsFormsEditorService service to display it:

using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System;

class MyType
{
private Foo foo = new Foo();
public Foo Foo { get { return foo; } }
}

[Editor(typeof(FooEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
class Foo
{
private string bar;
public string Bar
{
get { return bar; }
set { bar = value; }
}
}
class FooEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
IWindowsFormsEditorService svc = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
Foo foo = value as Foo;
if (svc != null && foo != null)
{
using (FooForm form = new FooForm())
{
form.Value = foo.Bar;
if (svc.ShowDialog(form) == DialogResult.OK)
{
foo.Bar = form.Value; // update object
}
}
}
return value; // can also replace the wrapper object here
}
}
class FooForm : Form
{
private TextBox textbox;
private Button okButton;
public FooForm() {
textbox = new TextBox();
Controls.Add(textbox);
okButton = new Button();
okButton.Text = "OK";
okButton.Dock = DockStyle.Bottom;
okButton.DialogResult = DialogResult.OK;
Controls.Add(okButton);
}
public string Value
{
get { return textbox.Text; }
set { textbox.Text = value; }
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Form form = new Form();
PropertyGrid grid = new PropertyGrid();
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
grid.SelectedObject = new MyType();
Application.Run(form);
}
}

Note: if you need to access something about the context of the property (the parent object etc), that is what the ITypeDescriptorContext (in EditValue) provides; it tells you the PropertyDescriptor and Instance (the MyType) that is involved.

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

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:

  • r-aghaei/GenericModalUITypeEditorSample

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

Property Grid Custom Editor

You need to define a class that inherits from UITypeEditor who's job will be to display Form1. This type is what goes into the EditorAttribute (not Form1). The sample here is more or less what you need to implement. Basically, you override GetEditorStyle to return UITypeEditorEditStyle.Modal and override EditValue to call IWindowsFormsEditorService.ShowDialog on an instance of your form.

Use a custom editor in a PropertyGrid for a type I cannot modify

Since you cannot change the code of your class, you can register a new type descriptor for your class at run-time to provide custom type description.

Using AssociatedMetadataTypeTypeDescriptionProvider you can create a type descriptor provider for your class that uses a metadata class to provide type description. Then you can register the provider using TypeDescriptor.AddProvider.

Example

Let's suppose the class which you cannot change its code is like this:

public class MyClass
{
public MyClass()
{
List = new List<string>();
}
public string Name { get; set; }
public List<string> List { get; set; }
}

The to solve the problem, you should define another class like that with the same properties and decorate those properties with attributes which you like, including the editor and converter attributes:

public class MyClassMetadata
{
[DisplayName("Name Property")]
public string Name { get; set; }

[Editor(@"System.Windows.Forms.Design.StringCollectionEditor," +
"System.Design, Version=2.0.0.0, Culture=neutral, " +
"PublicKeyToken=b03f5f7f11d50a3a",
typeof(System.Drawing.Design.UITypeEditor))]
public List<string> List { get; set; }
}

Then before showing the original class, register a new type descriptor provider for the original class which returns the metadata using the metadata class which we created:

private void Form1_Load(object sender, EventArgs e)
{
var provider = new AssociatedMetadataTypeTypeDescriptionProvider(typeof(MyClass),
typeof(MyClassMetadata));
TypeDescriptor.AddProvider(provider, typeof(MyClass));
this.propertyGrid1.SelectedObject = new MyClass();
}

I suppose you already have the CsvConverter from the first link:

public class CsvConverter : TypeConverter
{
// Overrides the ConvertTo method of TypeConverter.
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType)
{
List<String> v = value as List<String>;
if (destinationType == typeof(string))
{
return String.Join(",", v.ToArray());
}
return base.ConvertTo(context, culture, value, destinationType);
}
}

How do you create a custom collection editor form for use with the property grid?

Okay, I was finally able to track down how to accomplish this.

I was attempting to create a custom CollectionEditor.CollectionForm which was close to what I needed to do, but it wasn't quite the right direction.

First of all, create a regular Windows Form which includes your GUI for editing your collection. Then just include button/buttons which return a DialogResult in the Form.

Now the key to accomplishing what I was looking for is not a CollectionEditor.CollectionForm as I had thought would be the correct approach, but rather a UITypeEditor.

So, I created a class that inherited from the UITypeEditor. Then you simply flesh it out as such:

public class CustomCollectionModalEditor: UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
if (context ==null || context.Instance == null)
return base.GetEditStyle(context);

return UITypeEditorEditStyle.Modal;
}

public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
IWindowsFormsEditorService editorService;

if (context == null || context.Instance == null || provider == null)
return value;

editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));

CForm CollectionEditor = new CForm();

if (editorService.ShowDialog(CollectionEditor) == System.Windows.Forms.DialogResult.OK)
return CollectionEditor.Programmed;

return value;
//return base.EditValue(context, provider, value);
}
}

The key parts to take note of, are the functions GetEditStyle and EditValue. The part responsible for firing-off the Form you created to edit your collection, is in the EditValue override function.

CForm is the custom collection editor form I designed in this test to edit the collection. You need to fetch the IWindowsFormsEditorService associated with the IServiceProvider and simply call the .ShowDialog(formVariable) of the IWindowsFormsEditorService in order to show the form you designed to edit the collection. You can then catch the returned DialogResult value from your Form and perform any custom handling that you require.

I hope this helps someone out as it took me quite a bit of digging to determine the right way to incorporate this.

How to update PropertyGrid from a custom editor?

I found the solution myself:

The EditValue method of the custom editor should return a new object, not the passed in value object. Then the PropertyGrid automatically refreshes the other properties as well.

I did not need to use the RefreshProperties attribute anywhere.

How can I force the PropertyGrid to show a custom dialog for a specific property?

You need to set an [Editor(...)] for the property, giving it a UITypeEditor that does the edit; like so (with your own editor...)

using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;

static class Program
{
static void Main()
{
Application.Run(new Form { Controls = { new PropertyGrid { SelectedObject = new Foo() } } });
}
}

class Foo
{
[Editor(typeof(StringEditor), typeof(UITypeEditor))]
public string Bar { get; set; }
}

class StringEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
IWindowsFormsEditorService svc = (IWindowsFormsEditorService)
provider.GetService(typeof(IWindowsFormsEditorService));
if (svc != null)
{
svc.ShowDialog(new Form());
// update etc
}
return value;
}
}

You might be ablt to track down an existing Editor by looking at existing properties that behave like you want.



Related Topics



Leave a reply



Submit