I have a datagridView, that is bound to a List. This List is made up of my class which contains 2 public properties, a String Name, and another List CustomList. See below:
public class MyClass2
{
public string Name
{ get; set;}
public string Description
{
get;
set;
}
}
public class MyClass
{
List<MyClass2> myList;
public string Name
{
get;
set;
}
public List<MyClass2> CustomList
{
get { return myList ?? (myList= new List<MyClass2>()); }
}
}
And then in my designer page:
List<MyClass> myClassList = new List<MyClass>();
dataGridView.DataSource = myClassList;
As it is right now, the only column that appears in the grid, is the MyClass:Name column, and the CustomList column does not show up. What I'd like is the CustomList column to show and to display something like "Collection" with the "..." button showing, and when it is clicked to have the "Collection Editor" to popup.
Does anyone know if this is possible and how to enable it? If there's a tutorial or anything that would help me out I'd appreciate that too. Thanks.
Using generics, I think, is a clean solution:
public class Sorter<T>: IComparer<T>
{
public string Propiedad { get; set; }
public Sorter(string propiedad)
{
this.Propiedad = propiedad;
}
public int Compare(T x, T y)
{
PropertyInfo property = x.GetType().GetProperty(this.Propiedad);
if (property == null)
throw new ApplicationException("El objeto no tiene la propiedad " + this.Propiedad);
return Comparer.DefaultInvariant.Compare(property.GetValue(x, null), property.GetValue(y, null));
}
}
Usage example:
string orderBy = "propertyName";
bool orderAsc = true;
List<MyExampleClass> myClassList = someMethod();
if (!string.IsNullOrEmpty(orderBy))
{
myClassList.Sort(new Sorter<MyExampleClass>(orderBy));
if (!orderAsc) myClassList.Reverse();
}
Short answer: Yes, you can do it with some code.
Long answer: To write the code is gonna be a pain in the ass, as you would have to know not only how the DataGridView behaves with custom columns, but you would need to know how to expose design time elements at runtime, which requires quite a bit of plumbing. Extensive knowledge about the PropertyGrid must also be known.
Note: This might a fun component to write. (I might actually tackle it if I get some time)
So using the 'button' approach posted by Dave, and some code that I found that implements the CollectionEditor, I can edit the CustomList in MyClass2
Here's my solution, although not quite as clean as I'd like:
Put this class somewhere:
class MyHelper : IWindowsFormsEditorService, IServiceProvider, ITypeDescriptorContext
{
public static void EditValue(IWin32Window owner, object component, string propertyName)
{
PropertyDescriptor prop = TypeDescriptor.GetProperties(component)[propertyName];
if (prop == null) throw new ArgumentException("propertyName");
UITypeEditor editor = (UITypeEditor)prop.GetEditor(typeof(UITypeEditor));
MyHelper ctx = new MyHelper(owner, component, prop);
if (editor != null && editor.GetEditStyle(ctx) == UITypeEditorEditStyle.Modal)
{
object value = prop.GetValue(component);
value = editor.EditValue(ctx, ctx, value);
if (!prop.IsReadOnly)
{
prop.SetValue(component, value);
}
}
}
private readonly IWin32Window owner;
private readonly object component;
private readonly PropertyDescriptor property;
private MyHelper(IWin32Window owner, object component, PropertyDescriptor property)
{
this.owner = owner;
this.component = component;
this.property = property;
}
#region IWindowsFormsEditorService Members
public void CloseDropDown()
{
throw new NotImplementedException();
}
public void DropDownControl(System.Windows.Forms.Control control)
{
throw new NotImplementedException();
}
public System.Windows.Forms.DialogResult ShowDialog(System.Windows.Forms.Form dialog)
{
return dialog.ShowDialog(owner);
}
#endregion
#region IServiceProvider Members
public object GetService(Type serviceType)
{
return serviceType == typeof(IWindowsFormsEditorService) ? this : null;
}
#endregion
#region ITypeDescriptorContext Members
IContainer ITypeDescriptorContext.Container
{
get { return null; }
}
object ITypeDescriptorContext.Instance
{
get { return component; }
}
void ITypeDescriptorContext.OnComponentChanged()
{ }
bool ITypeDescriptorContext.OnComponentChanging()
{
return true;
}
PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
{
get { return property; }
}
#endregion
Add a button column to the data grid:
DataGridViewButtonColumn butt = new DataGridViewButtonColumn();
butt.HeaderText = "CustomList";
butt.Name = "CustomList";
butt.Text = "Edit CustomList...";
butt.UseColumnTextForButtonValue = true;
dataGridView.Columns.Add(butt);
dataGridView.CellClick += new DataGridViewCellEventHandler(dataGridView_CellClick);
Then call it in the button handler of the cell click.
if (e.RowIndex < 0 || e.ColumnIndex != dataGridView.Columns["CustomList"].Index)
return;
//get the name of this column
string name = (string)dataGridView[dataGridView.Columns["Name"].Index, e.RowIndex].Value;
var myClassObject= myClassList.Find(o => o.Name == name);
MyHelper.EditValue(this, myClassObject, "CustomList");
I'd still be interested in hearing other approaches, and not having to implement my own CollectionEditor. And I'm still interested in having it look more like what the TabControl uses to add TabPages in the PropertyGrid...by displaying the "..." button...but this might work for now.
What you want to do is add a column template with a button in it:
http://geekswithblogs.net/carmelhl/archive/2008/11/11/126942.aspx
In the handler for the button, get the selected MyClass item from the collection and bind its list property to a grid in your popup.
Related
I'd like to use the functionality of a ComboBox as edit option for a var in the properties window of a custom control / component. Not the ComboBox component itself.
As example:
private string[] _stringArray = { "string0", "string1" };
public string[] StringArray
{
get { return _stringArray; }
//callback
//set { _stringArray = value; }
}
As you know this will give me the object browser as view/edit option in the property window.
Funny thing that I can edit the values even with no setter.
In my researches I found out that it is possible ("UITypeEditorEditStyle.DropDown"). But I have no idea how to implement that.
Or what [Instructions] I could set for the "StringArray".
My final goal is a copy of the object selector drop-down of visual studio as a property parameter:
With custom event handling of course. But as you see I'm far away to realize that. :(
I have been looking for a tutorial on the following topics for a long time:
[Designer] instructions reference
A basic tutorial how to manage the display style of properties ✔
However I'm tired of my unsuccessful researches. Some good links are always welcome.
UPDATE:
After I more or less understood the principle (from the link in the comments, thanks) I came to an interim solution.
I realized that I need at least an int var to set a selected `index`. I thought / hoped that VS can do this automatically. Like it does with enums. And my lack of knowledge concerning [Instructions].
I could define a string variable as a placeholder for the selected index of the array in order to do without the TypeConverter, but that would make even less sense. I really don't need another abstract variable for nothing.
So the basis drop-down, which e.g. can display enums directly, does not appear to be applicable. So they use a trick with "UITypeEditorEditStyle.DropDown", which actually isn't a drop-down. It's just a button where you can place the control of your choice. In my case a ListView. Since the "drop" of the "down" already exists. Looks like cheating. ;)
//...
[TypeConverter(typeof(StringArrayConverter))]
public interface IStringArray
{
int SelectedIndex { get; set; }
string[] StringArray { get; set; }
}
public class DropDownStringArray : IStringArray
{
private string[] _stringArray = { "string0", "string1", "string2", "string3", "string4", "string5", "string6" };
public int SelectedIndex { get; set; }
public string[] StringArray
{
get { return _stringArray; }
set { _stringArray = value; }
}
}
private DropDownStringArray _ddsa = new DropDownStringArray();
[Editor(typeof(StringArrayTypeEditor), typeof(UITypeEditor))]
public DropDownStringArray MyDropDownStringArray
{
get { return _ddsa; }
set { _ddsa = value; }
}
//...
public class StringArrayConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
var sa = value as IStringArray;
if (sa != null) { return sa.StringArray[sa.SelectedIndex]; }
}
return "(none)";
}
}
public class StringArrayTypeEditor : UITypeEditor
{
private IWindowsFormsEditorService _editorService;
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) { return UITypeEditorEditStyle.DropDown; }
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
_editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
DropDownStringArray ddsa = (DropDownStringArray)value;
ListBox lb = new ListBox();
lb.SelectionMode = SelectionMode.One;
for (int i = 0; i < ddsa.StringArray.Length; i++) { lb.Items.Add(ddsa.StringArray[i]); }
lb.SetSelected(ddsa.SelectedIndex, true);
lb.SelectedValueChanged += OnListBoxSelectedValueChanged;
_editorService.DropDownControl(lb);
if (lb.SelectedItem != null) { ddsa.SelectedIndex = lb.SelectedIndex; }
return value;
}
private void OnListBoxSelectedValueChanged(object sender, EventArgs e)
{
_editorService.CloseDropDown();
}
}
Which actually copy the entire class just to change the SelectedIndex. The right thing would be to abuse the SelectedIndex and convert it to a string or something like that. I think I do not care about that anymore. Rather to catch some fresh air. ;)
Maybe that will help someone else.
Note: This is not a practical propose. As example SelectedIndex will not be updated if you change the (length) of the array. I've choosen string[] because it's a really basic and well known type. I am aware that my "program" has no real use. It was just about understanding the principle.
Hi all i am using mvvmcross and portable class libraries , so i cannot use prism or componentmodel data annotations, to validate my classes. basically i have a modelbase that all my models inherit from.
My validate code below is horribly broken, basically im looking for the code that data annotations uses to iterate thru all the properties on my class that is inheriting the base class ,
i have written various attributes that are there own validators inheriting from "validatorBase" which inherits from attribute. i just cannot for the life of me figure out thecode that says ... ok im a class im going to go through all the properties in me that have an attribute of type ValidatorBase and run the validator. my code for these are at the bottom
public class ModelBase
{
private Dictionary<string, IEnumerable<string>> _errors;
public Dictionary<string, IEnumerable<string>> Errors
{
get
{
return _errors;
}
}
protected virtual bool Validate()
{
var propertiesWithChangedErrors = new List<string>();
// Get all the properties decorated with the ValidationAttribute attribute.
var propertiesToValidate = this.GetType().GetRuntimeProperties()
.Where(c => c.GetCustomAttributes(typeof(ValidatorBase)).Any());
foreach (PropertyInfo propertyInfo in propertiesToValidate)
{
var propertyErrors = new List<string>();
TryValidateProperty(propertyInfo, propertyErrors);
// If the errors have changed, save the property name to notify the update at the end of this method.
bool errorsChanged = SetPropertyErrors(propertyInfo.Name, propertyErrors);
if (errorsChanged && !propertiesWithChangedErrors.Contains(propertyInfo.Name))
{
propertiesWithChangedErrors.Add(propertyInfo.Name);
}
}
// Notify each property whose set of errors has changed since the last validation.
foreach (string propertyName in propertiesWithChangedErrors)
{
OnErrorsChanged(propertyName);
OnPropertyChanged(string.Format(CultureInfo.CurrentCulture, "Item[{0}]", propertyName));
}
return _errors.Values.Count == 0;
}
}
here is my validator
public class BooleanRequired : ValidatorBase
{
public override bool Validate(object value)
{
bool retVal = true;
retVal = value != null && (bool)value == true;
var t = this.ErrorMessage;
if (!retVal)
{
ErrorMessage = "Accept is Required";
}
return retVal;
}
}
and here is an example of its usage
[Required(ErrorMessage = "Please enter the Amount")]
public decimal Amount
{
get { return _amount; }
set { _amount = value; }//SetProperty(ref _amount, value); }
}
I have an problem with this three classes.
In first class i'm extending for listview class for common method
In second class we put one method it's invoke by the 1st class
This is fine for above two classes but in my third class need to pass that T, M values.
But i don't understand how to do this?
anybody help this issue?
Thank you
1st Class
public class MyListView : ListView
{
public UserControl uc { get; set; }
internal MyLEvent<Type,Type> MyLEvnt { get; set; }
public MyListView()
{
PreviewKeyDown += new KeyEventHandler(MyListView_PreviewKeyDown);
}
private void MyListView_PreviewKeyDown(object sender,KeyEventArgs e)
{
ListView view = sender as ListView;
var item = view.SelectedItem;
if (item != null)
{
string str = item.GetType().Name;
if (e.Key == Key.Delete)
{
MyLEvnt.Method(item, "Delete");
}
else if (e.Key == Key.Enter)
{
MyLEvnt.Method(item, "Modify");
uc.GetType().GetProperty("Update").SetValue(uc, 1, null);
MethodInfo mi = uc.GetType().GetMethod("IClear");
mi.Invoke(uc, null);
}
}
}
}
2nd Class
public class MyLEvent<T,M> where T : class where M : class
{
private M manager;
private T type;
public MyLEvent()
{
}
public object Method(object _view, string flog)
{
object retVal = null;
type = Activator.CreateInstance<T>();
manager = Activator.CreateInstance<M>();
if (flog == "Modify")
{
MethodInfo method = typeof(M).GetMethod("getData");
type = (T)method.Invoke(manager, new[] { _view });
}
else if (flog == "Set")
{
MethodInfo method = typeof(M).GetMethod("setDefault");
retVal = method.Invoke(manager, new[] { _view });
}
else
{
if (MyMessage.askDelete() == true)
{
PropertyClass.Properties(_view, type, 'U');
MethodInfo method = typeof(M).GetMethod("Delete");
retVal = method.Invoke(manager, new[] { type });
}
}
return retVal;
}
}
3rd Class
public partial class SubASettings : UserControl
{
public SubASettings()
{
InitializeComponent();
MAILV.uc = this;
MAILV.MyLEvnt = new MyLEvent<typeof(InvMail), MailManager>();
Clear();
}
}
Thank you,
You can add a constraint to your generic type by declaring an interface:
public interface IManager
{
void getData();
setDefault
Delete
}
Define this constraint in declaration of second class which means that M type should implement IManager interface:
public class MyLEvent<T, M>
where T : class
where M : class, IManager
Then, you can invoke members of your class which defined in the interface:
public class MyLEvent<T, M>
where T : class
where M : class, IManager
{
private M manager;
public MyLEvent()
{
manager.Delete();
}
}
The declaration:
public class MyLEvent<T,M> where T : class where M : class
{
...
}
...defines a generic type that is used to create concrete types when provided with the appropriate type parameters. You cannot use generic types directly, you can only use them to create concrete types which can then be used.
For example, the List<T> generic type defines structure and code that can be used to create a variety of concrete types depending on the type parameter you use. List<string> is a concrete type created from the List<T> generic type.
In the case of your MyLEvent generic, there are two type parameters: T and M. You need to specify both of those to create a concrete type that can be used.
In your MyListView class you define the MyLEvnt field like this:
internal MyLEvent<Type,Type> MyLEvnt { get; set; }
This defines the MyLEvnt field as an instance of the concrete type MyLEvent<Type, Type>. Note that Type is a class that is used to access information about types. In this usage it is not a way to avoid supplying a type parameter, it is a type parameter.
In your third class you then do this:
MAILV.MyLEvnt = new MyLEvent<typeof(InvMail), MailManager>();
Even when we take the typeof() out of it, this will fail because you are attempting to assign an instance of MyLEvent<InvMail, MailManager> to a field of type MyLEvent<Type, Type>. These are different types, just as List<string> is different from List<int>.
You need to read the MSDN articles on Generics. These explain the details of how generics work and give you a lot of examples of how to use them and why.
Thank you so much for supporting. And i got one solution for this....
This is my previous 1st class
public class MyListView : ListView
{
public UserControl uc { get; set; }
internal MyLEvent<Type,Type> MyLEvnt { get; set; }
public MyListView()
{
PreviewKeyDown += new KeyEventHandler(MyListView_PreviewKeyDown);
}
private void MyListView_PreviewKeyDown(object sender,KeyEventArgs e)
{
ListView view = sender as ListView;
var item = view.SelectedItem;
if (item != null)
{
string str = item.GetType().Name;
if (e.Key == Key.Delete)
{
MyLEvnt.Method(item, "Delete");
}
else if (e.Key == Key.Enter)
{
MyLEvnt.Method(item, "Modify");
uc.GetType().GetProperty("Update").SetValue(uc, 1, null);
MethodInfo mi = uc.GetType().GetMethod("IClear");
mi.Invoke(uc, null);
}
}
}
}
After modified in my level....
public class MyListView : ListView
{
public UserControl uc { get; set; }
public object ML { get; set; }
public MyListView()
{
PreviewKeyDown += new KeyEventHandler(MyListView_PreviewKeyDown);
}
public void Methode<T,M>() where T : class where M : class
{
ListViewEvents<T, M> mn = new ListViewEvents<T, M>();
ML = mn;
}
private void MyListView_PreviewKeyDown(object sender,KeyEventArgs e)
{
ListView view = sender as ListView;
var item = view.SelectedItem;
if (item != null)
{
string str = item.GetType().Name;
if (e.Key == Key.Delete)
{
MethodInfo mi = ML.GetType().GetMethod("Method");
mi.Invoke(ML,new[]{item,"Delete"});
MethodInfo m = uc.GetType().GetMethod("IClear");
m.Invoke(uc, null);
}
else if (e.Key == Key.Enter)
{
MethodInfo m = ML.GetType().GetMethod("Method");
object ob = m.Invoke(ML, new[] { item, "Modify" });
PropertyClass.Properties(uc, ob, 'U');
}
}
}
}
This solution is better working for me. But if is there any wrong with this
please let me guide
Thank you again...
Per the below sample code posted I am able to see the values as Risk and Default in the dropdown.
But since I have a setting [DefaultValue("Risk")] above the property named "DummyProperty" I would expect the Risk value selected in the Property Grid Dropdown. But it’s not happening. What am I missing here?
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
string sDummy;
[DefaultValue("Risk")]
[Category("Test")]
[ParamDesc("SystemType")]
[TypeConverter(typeof(PropertyGridTypeConverter))]
public String DummyProperty
{
get { return sDummy; }
set { sDummy = value; }
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class ParamDesc : Attribute
{
public ParamDesc(string PD)
{ PropDesc = PD; }
public string PropDesc
{ get; set; }
}
class PropertyGridTypeConverter : TypeConverter
{
List<string> lst = new List<string>();
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
if (context != null)
{
AttributeCollection ua = context.PropertyDescriptor.Attributes;
ParamDesc cca = (ParamDesc)ua[typeof(ParamDesc)];
switch (cca.PropDesc)
{
case "SystemType":
lst = new List<string> {"Risk", "Default"};
break;
case "DateType":
lst = new List<string> {"Daily", "Monthly"};
break;
}
}
lst.Sort();
return new StandardValuesCollection(lst);
}
}
Somewhat confusingly, the DefaultValue custom attribute isn't used to set default values on properties like you want. In fact, it isn't directly used by the runtime at all. It's intended instead for use by the Visual Studio designer.
You'll probably just want to initialize the value elsewhere (such as in the UserControl1 constructor).
More information here:
.Net DefaultValueAttribute on Properties
I'm trying to customize a property wherein if I click the elipses button [...] a new dialog form will be displayed. Unfortunately, the form won't show. Can you please check the below code and advise where did I go wrong?
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System;
namespace Test01
{
/// <summary>
/// Description of MainForm.
/// </summary>
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
void MainFormLoad(object sender, EventArgs e)
{
Form form = new Form();
propertyGrid1.SelectedObject = new MyType();
}
}
class MyType
{
private string bar;
[Editor(typeof(FooEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
public string Bar
{
get { return bar; }
set { bar = value; }
}
}
[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; }
}
}
}
Your editor works with the Foo type (if value isn't Foo then it won't show the dialog) but you create an instance of MyType, it contains one property Bar of type string then it can't be edited by your FooEditor.
To try how your code works you should change the property Bar from string to Foo:
class MyType
{
private Foo bar = new Foo();
[Editor(typeof(FooEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
public Foo Bar
{
get { return bar; }
set { bar = value; }
}
}
EXAMPLES
Let's see two examples. In the first one you edit the property where the Attribute has been applied (then your editor changes the value of the property itself):
This is the class you'll edit in the PropertyGrid, I removed the Foo class because useless for this example:
class MyType
{
private string bar;
[Editor(typeof(MyStringEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
public string Bar
{
get { return bar; }
set { bar = value; }
}
}
This is the editor that will edit your Bar property. Actually it works with any property of type string:
class MyStringEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
var svc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
string text = value as string;
if (svc != null && text != null)
{
using (FooForm form = new FooForm())
{
form.Value = text;
if (svc.ShowDialog(form) == DialogResult.OK)
{
return form.Value;
}
}
}
return value;
}
}
Now another example, the editor doesn't change the property value itself but the value of a property of that property (editor is applied to property MyType.Bar (of type Foo) but it changes the value of the property Value of Foo.
Let's introduce again a complex type for your Bar property:
class Foo
{
private string _value;
private object _tag; // Unused in this example
public string Value
{
get { return _value; }
set { _value = value; }
}
public object Tag
{
get { return _tag; }
set { _value = _tag; }
}
}
Change the MyType class to publish one property of the complex type we wrote, note that the EditorAttribute now uses a new editor specific for the Foo type:
class MyType
{
private Foo bar = new Foo();
[Editor(typeof(FooEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
public Foo Bar
{
get { return bar; }
set { bar = value; }
}
}
Finally we write the editor for the Foo type. Please note that this editor will change only the value of the property Foo.Value, the other property exposed by Foo won't be touched:
class FooEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
var svc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
Foo foo = value as Foo;
if (svc != null && foo != null)
{
using (FooForm form = new FooForm())
{
form.Value = foo.Value;
if (svc.ShowDialog(form) == DialogResult.OK)
{
// Updates the value of the property Value
// of the property we're editing.
foo.Value = form.Value;
}
}
}
// In this case we simply return the original property
// value, the property itself hasn't been changed because
// we updated the value of an inner property
return value;
}
}
Last notes: in the if block of svc.ShowDialog() we have the update value from the Form, the property Bar (where the editor is applied) won't be changed but we'll update the Value property of the Foo instance it contains. Roughly equivalent to write something like this:
MyType myType = new MyType();
myType.Bar.Value = form.Value;
If we just return a new value for the property (like in the previous example) the old value of Tag property will be lost, something like this:
MyType myType = new MyType();
Foo foo = new Foo();
foo.Value = form.Value;
myType.Bar = foo;
Can you see the difference? Anyway it's going to be too more a tutorial than an answer...
Please note that examples are untested, I just wrote them here, I just would like to expose the concept more than to provide a surely working example.
REFERENCES
Here you can find a short list of useful resources:
A comprehensive example about how UITypeEditor works
Main source for PropertyDescriptor documentation is always MSDN but here you can find a pretty short example.
If you just need to localize property names you may take a look to this entry here on SO.