yonan2236
yonan2236

Reputation: 13659

Customizing PropertyGrid Control

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

Upvotes: 1

Views: 4368

Answers (1)

Adriano Repetti
Adriano Repetti

Reputation: 67148

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.

Upvotes: 3

Related Questions