How do I inject a custom UITypeEditor for all properties of a closed-source type?
Asked Answered
H

3

9

I want to avoid placing an EditorAttribute on every instance of a certain type that I've written a custom UITypeEditor for.

I can't place an EditorAttribute on the type because I can't modify the source.

I have a reference to the only PropertyGrid instance that will be used.

Can I tell a PropertyGrid instance (or all instances) to use a custom UITypeEditor whenever it encounters a specific type?

Here is an MSDN article that provides are starting point on how to do this in .NET 2.0 or greater.

Henleigh answered 11/5, 2009 at 17:45 Comment(0)
S
24

You can usually associate editors etc at runtime via TypeDescriptor.AddAttributes. For example (the Bar property should show with a "..." that displays "Editing!"):

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

class Foo
{
    public Foo() { Bar = new Bar(); }
    public Bar Bar { get; set; }
}
class Bar
{
    public string Value { get; set; }
}

class BarEditor : UITypeEditor
{
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.Modal;
    }
    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
    {
        MessageBox.Show("Editing!");
        return base.EditValue(context, provider, value);
    }
}
static class Program
{
    [STAThread]
    static void Main()
    {
        TypeDescriptor.AddAttributes(typeof(Bar),
            new EditorAttribute(typeof(BarEditor), typeof(UITypeEditor)));
        Application.EnableVisualStyles();
        Application.Run(new Form { Controls = { new PropertyGrid { SelectedObject = new Foo() } } });
    }
}
Shoshone answered 11/5, 2009 at 19:56 Comment(3)
Marc, well done. I would have advised to create a custom type descriptor and the provider to publish it, but your method is a good shortcut to register the provider behind the scene and inject the editor !! Learnt something.Swee
Wow, that's very simple in practice. Thanks!Henleigh
This is perfect! I'm working on a drawing library and wanted to provide PropertyGrid editor support for objects without taking a dependency on Windows Forms from the object library in order to decorate the properties. This solution allows me to create the editors outside the core library and add them at runtime.Battologize
U
4

Marc's solution bluntly applies the EditorAttribute to the Bar type globally. If you have a delicate disposition, you might rather annotate properties of a specific instances. Alas, that isn't possible with TypeDescriptor.AddAttributes

My solution was to write a wrapper ViewModel<T>, which copies properties from T, annotating some with extra attributes. Suppose we have a variable datum of type Report, we'd use it like this

        var pretty = ViewModel<Report>.DressUp(datum);
        pretty.PropertyAttributeReplacements[typeof(Smiley)] = new List<Attribute>() { new EditorAttribute(typeof(SmileyEditor),typeof(UITypeEditor))};
        propertyGrid1.SelectedObject = pretty;

Where ViewModel<T> is defined:

public class ViewModel<T> : CustomTypeDescriptor
{
    private T _instance;
    private ICustomTypeDescriptor _originalDescriptor;
    public ViewModel(T instance, ICustomTypeDescriptor originalDescriptor) : base(originalDescriptor)
    {
        _instance = instance;
        _originalDescriptor = originalDescriptor;
        PropertyAttributeReplacements = new Dictionary<Type,IList<Attribute>>();
    }

    public static ViewModel<T> DressUp(T instance)
    {
        return new ViewModel<T>(instance, TypeDescriptor.GetProvider(instance).GetTypeDescriptor(instance));
    }

    /// <summary>
    /// Most useful for changing EditorAttribute and TypeConvertorAttribute
    /// </summary>
    public IDictionary<Type,IList<Attribute>> PropertyAttributeReplacements {get; set; } 

    public override PropertyDescriptorCollection GetProperties (Attribute[] attributes)
    {
        var properties = base.GetProperties(attributes).Cast<PropertyDescriptor>();

        var bettered = properties.Select(pd =>
            {
                if (PropertyAttributeReplacements.ContainsKey(pd.PropertyType))
                {
                    return TypeDescriptor.CreateProperty(typeof(T), pd, PropertyAttributeReplacements[pd.PropertyType].ToArray());
                }
                else
                {
                    return pd;
                }
            });
        return new PropertyDescriptorCollection(bettered.ToArray());
    }

    public override PropertyDescriptorCollection GetProperties()
    {
        return GetProperties(null);
    }
}

As defined above, this substitutes properties of a specific type, but you can substitute properties by name if you need the greater resolution.

Unchaste answered 25/9, 2012 at 16:6 Comment(2)
Since decided this is way too much faff, and to use the much simpler global solution.Unchaste
Went back to using this, but WARNING it might be incompatible with objects implementing ICustomTypeDescriptorUnchaste
A
0

Just add an Editor attribute to your class.

Acceptant answered 11/5, 2009 at 17:51 Comment(1)
Great idea, but I forgot to mention that I can't place an EditorAttribute on the type because I can't modify the source.Henleigh

© 2022 - 2024 — McMap. All rights reserved.