views:

647

answers:

3

How to add the EditorAttribute (Editor) to an object's property at run-time?

I have My.Settings.ExcludeFiles, which is created by the settings designer as Public Property ExcludedFiles() As Global.System.Collections.Specialized.StringCollection. When editing ExcludedFiles via a property grid, the "String Collection Editor" generates a "Constructor on type 'System.String' not found" run-time exception.

I cannot change the attributes of the ExcludeFiles property because they will be overwritten the next time any setting changes are made. Therefore, I must attach/add the Editor/EditorAttribute at run-time.

What I want to do is add the StringCollectionEditor at run-time, shown below as design-time attribute.

    <Editor(GetType(StringCollectionEditor), GetType(UITypeEditor))> _

Solutions

Method #1

TypeDescriptor.AddAttributes( _
    GetType(Specialized.StringCollection), _
    New EditorAttribute( _
        "System.Windows.Forms.Design.StringCollectionEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", _
         GetType(System.Drawing.Design.UITypeEditor)))

You only have to add this attribute once, such as application initialization.

Method #2

More flexible. See Nicolas Cadilhac answer below at http://stackoverflow.com/questions/2043579/adding-editor-editorattribute-at-run-time-dynamically-to-an-objects-property/2044673#2044673. It uses derived CustomTypeDescriptor and TypeDescriptionProvider classes. You only have to add the provider once, such as application initialization.

A: 

You can't. An attribute can only be defined at compile time (unless you generate the type dynamically of course)

Thomas Levesque
This doesn't make sense in the realm of .NET where nearly everything can be done at run-time. There are plenty of examples out there that do this. I just cannot find anything simple that illustrates this specific case.
AMissico
Well, wait for new answers then, perhaps someone knows a way to do it... but I seriously doubt it. Attributes are *static* metadata, there is no way to modify existing types at runtime (but you can create new types dynamically, derived from existing types ; this is used to generate transparent proxies, for instance)
Thomas Levesque
I believe that is what `TypeDescriptor` is for. Article does it, but is hard to read and not a simple example. <http://minalabib.wordpress.com/2009/11/08/run-time-dynamic-attributes-in-c-2/>
AMissico
You don't have to accept my answer, and I don't have to delete it ;). Nicolas's answer is very enlightening, but as I mentioned in a comment to his answer, this technique doesn't work for the general case : it has no effect on what you can see using reflection. The type is not actually modified, only the perception of that type via TypeDescriptor is affected. So my answer still stands, although I admit it's not very useful for your needs ;)
Thomas Levesque
A: 

Yes it is possible to dynamically change a TypeDescriptor so that you return the UITypeEditor you want. This is explained in this article. But note that it will add it for all properties of this type.

I grabbed the code from here and roughly changed it for the following:

private class StringCollectionTypeDescriptor : CustomTypeDescriptor
{
    private Type _objectType;
    private StringCollectionTypeDescriptionProvider _provider;

    public StringCollectionTypeDescriptor(
        StringCollectionTypeDescriptionProvider provider,
        ICustomTypeDescriptor descriptor, Type objectType)
        :
        base(descriptor)
    {
        if (provider == null) throw new ArgumentNullException("provider");
        if (descriptor == null)
            throw new ArgumentNullException("descriptor");
        if (objectType == null)
            throw new ArgumentNullException("objectType");
        _objectType = objectType;
        _provider = provider;
    }

    /* Here is your customization */
    public override object GetEditor(Type editorBaseType)
    {
        return new MultilineStringEditor();
    }
}

public class StringCollectionTypeDescriptionProvider : TypeDescriptionProvider
{
    private TypeDescriptionProvider _baseProvider;

    public StringCollectionTypeDescriptionProvider(Type t)
    {
        _baseProvider = TypeDescriptor.GetProvider(t);
    }

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
        return new StringCollectionTypeDescriptor(this, _baseProvider.GetTypeDescriptor(objectType, instance), objectType);
    }
}

Then you register your provider:

TypeDescriptor.AddProvider(new StringCollectionTypeDescriptionProvider
    (typeof(System.Collections.Specialized.StringCollection)),
    typeof(System.Collections.Specialized.StringCollection));

This works well, except that it will make you discover that you have another issue: MultilineStringEditor is an editor that works with the String type, not with the StringCollection type. What you actually need is the private StringCollectionEditor in the .Net framework. So let's replace GetEditor by:

public override object GetEditor(Type editorBaseType)
{
    Type t = Type.GetType("System.Windows.Forms.Design.StringCollectionEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
    return TypeDescriptor.CreateInstance(null, t, new Type[] { typeof(Type) }, new object[] { typeof(string) });
}

I hope this helps.

Nicolas Cadilhac
YOU ARE AWESOME! Simple, to the point, provided reference and code, and you even caught my MultilineStringEditor reference mistake. I knew MultilineStringEditor wouldn't work because I already tried it using design-time attribute. I figured once I got code working, I would determine the proper editor to use.
AMissico
I can do this with a PropertyDescriptor also, correct? If so, out of curiousity, do you have code?
AMissico
yes you could but this is far longer. And btw, look at my other answer !! ;)
Nicolas Cadilhac
+1  A: 

After giving you my first answer, I remembered another solution given by Marc Gravell that I even commented. Believe it or not, you just need to call TypeDescriptor.AddAttributes().

This is here: How do I inject a custom UITypeEditor for all properties of a closed-source type?.

For your case it gives:

TypeDescriptor.AddAttributes(
    typeof(StringCollection),
    new EditorAttribute("System.Windows.Forms.Design.StringCollectionEditor,
        System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
        typeof(UITypeEditor)))

So maybe you should uncheck my previous answer and confirm this one as the solution (although all the credit goes to Marc). But my previous post still gives you a good technique when you need to do more complex stuff with a TypeDescriptor.

Nicolas Cadilhac
I am not sure it will work because it uses an editor for a class and not a property. I did try this earlier, but couldn't get it working. Now that I have an example, I will try again later and test. You first answer is nice because it allows for .NET internal editors to be used.
AMissico
By passing the StringCollectionEditor type as I did, Marc's example will work too, maybe even with only the namespace and assembly. Note that my solution is also a one for a Type (StringCollection) and not for just one property. For a specific property only you would need to publish a custom PropertyDescriptor from a custom TypeDescriptor.
Nicolas Cadilhac
uh, nice... I never realized this was possible. However it will only work when types are examined with `TypeDescriptor`, not with "real" reflection : for instance `Attribute.GetCustomAttributes` won't return attributes added with `TypeDescriptor.AddAttributes`. Anyway, in that case it perfectly achieves the desired goal, so +1 ;)
Thomas Levesque