How to Add Property-Level Attribute to the Typedescriptor at Runtime

How to add property-level Attribute to the TypeDescriptor at runtime?

Unlike others have suggested, it's quite possible, and also not that hard. For example, you want to add some new attributes to some properties, which you can select at runtime based on some criteria.

There're two helper classes we'll need to implement this.

First goes PropertyOverridingTypeDescriptor, it allows us to supply our own property descriptors for some properties, while keeping others intact:

public class PropertyOverridingTypeDescriptor : CustomTypeDescriptor
{
private readonly Dictionary<string, PropertyDescriptor> overridePds = new Dictionary<string, PropertyDescriptor>();

public PropertyOverridingTypeDescriptor(ICustomTypeDescriptor parent)
: base(parent)
{ }

public void OverrideProperty(PropertyDescriptor pd)
{
overridePds[pd.Name] = pd;
}

public override object GetPropertyOwner(PropertyDescriptor pd)
{
object o = base.GetPropertyOwner(pd);

if (o == null)
{
return this;
}

return o;
}

public PropertyDescriptorCollection GetPropertiesImpl(PropertyDescriptorCollection pdc)
{
List<PropertyDescriptor> pdl = new List<PropertyDescriptor>(pdc.Count+1);

foreach (PropertyDescriptor pd in pdc)
{
if (overridePds.ContainsKey(pd.Name))
{
pdl.Add(overridePds[pd.Name]);
}
else
{
pdl.Add(pd);
}
}

PropertyDescriptorCollection ret = new PropertyDescriptorCollection(pdl.ToArray());

return ret;
}

public override PropertyDescriptorCollection GetProperties()
{
return GetPropertiesImpl(base.GetProperties());
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetPropertiesImpl(base.GetProperties(attributes));
}
}

Few remarks:

  • Constructor takes ICustomTypeDescriptor, no worries here, we can get one for any type or it's instance with the TypeDescriptor.GetProvider(_settings).GetTypeDescriptor(_settings) where _settings can be either Type or object of that type.
  • OverrideProperty does just what we need, more on it later.

The other class we need is the TypeDescriptionProvider that will return our custom type descriptor instead of the default one. Here it is:

public class TypeDescriptorOverridingProvider : TypeDescriptionProvider
{
private readonly ICustomTypeDescriptor ctd;

public TypeDescriptorOverridingProvider(ICustomTypeDescriptor ctd)
{
this.ctd = ctd;
}

public override ICustomTypeDescriptor GetTypeDescriptor (Type objectType, object instance)
{
return ctd;
}
}

Fairly simple: you just supply the type descriptor instance on construction and here you go.

And finally, processing code. For example, we want all properties ending with ConnectionString in our object (or type) _settings to be editable with the System.Web.UI.Design.ConnectionStringEditor. To achieve that, we can use this code:

// prepare our property overriding type descriptor
PropertyOverridingTypeDescriptor ctd = new PropertyOverridingTypeDescriptor(TypeDescriptor.GetProvider(_settings).GetTypeDescriptor(_settings));

// iterate through properies in the supplied object/type
foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(_settings))
{
// for every property that complies to our criteria
if (pd.Name.EndsWith("ConnectionString"))
{
// we first construct the custom PropertyDescriptor with the TypeDescriptor's
// built-in capabilities
PropertyDescriptor pd2 =
TypeDescriptor.CreateProperty(
_settings.GetType(), // or just _settings, if it's already a type
pd, // base property descriptor to which we want to add attributes
// The PropertyDescriptor which we'll get will just wrap that
// base one returning attributes we need.
new EditorAttribute( // the attribute in question
typeof (System.Web.UI.Design.ConnectionStringEditor),
typeof (System.Drawing.Design.UITypeEditor)
)
// this method really can take as many attributes as you like,
// not just one
);

// and then we tell our new PropertyOverridingTypeDescriptor to override that property
ctd.OverrideProperty(pd2);
}
}

// then we add new descriptor provider that will return our descriptor instead of default
TypeDescriptor.AddProvider(new TypeDescriptorOverridingProvider(ctd), _settings);

That's it, now all properties ending with ConnectionString will be editable through ConnectionStringEditor.

As you can see, we just override some functionality of the default implementation every time, so the system should be fairly stable and behave as expected.

Add property-level attributes dynamically using TypeDescriptor for PropertyGrid

Actually It is possible, but I have to admit it is rather obscure and poorly documented. The key is to derive your own custom TypeDescriptorProvider from System.ComponentModel.TypeDescriptionProvider. Then you can return your own TypeDescriptor descendant.

I did this to fix a rather nasty limitation regarding ViewModels and metadata in ASP.NET MVC, but you can just as well use it to insert your own extra metadata.

Add display attribute to a variable on runtime

Very simple example, it can be build and in PropertyGrid you will see something.
You need read about ICustomTypeDescriptor and PropertyDescriptor.

On Form Load:

propertyGrid1.SelectedObject = new MyType(new[] { "Property1", "Property2" });

Types:

public class MyType : ICustomTypeDescriptor
{
private string[] _properties;
public MyType(string[] properties)
{
_properties = properties;
}

public AttributeCollection GetAttributes()
{
return null;
}

public string GetClassName()
{
return nameof(MyType);
}

public string GetComponentName()
{
throw new NotImplementedException();
}

public TypeConverter GetConverter()
{
return null;
}

public EventDescriptor GetDefaultEvent()
{
throw new NotImplementedException();
}

public PropertyDescriptor GetDefaultProperty()
{
return null;
}

public object GetEditor(Type editorBaseType)
{
return null;
}

public EventDescriptorCollection GetEvents()
{
throw new NotImplementedException();
}

public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
throw new NotImplementedException();
}

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

public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var props = new PropertyDescriptor[_properties.Length];
for (int i = 0; i < _properties.Length; i++)
props[i] = new CustomPropertyDescriptor(_properties[i],
new Attribute[]
{
new DisplayNameAttribute(@"Displ Value " + i),
new CategoryAttribute("Category" + i%2)
});
return new PropertyDescriptorCollection(props);
}

public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
}

public class CustomPropertyDescriptor : PropertyDescriptor
{
public CustomPropertyDescriptor(string name, Attribute[] attrs) : base(name, attrs)
{
}

public override bool CanResetValue(object component)
{
return true;
}

public override object GetValue(object component)
{
return "1";
}

public override void ResetValue(object component)
{
throw new NotImplementedException();
}

public override void SetValue(object component, object value)
{
throw new NotImplementedException();
}

public override bool ShouldSerializeValue(object component)
{
return false;
}

public override Type ComponentType { get; }
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType { get { return typeof (string); } }
}

How can I add CustomAttribute to all object properties dynamically?

I'm having some trouble understanding your question, but let me attempt to clarify.

I have an object with multiple attributes
...
it will erase all CustomAttributes

In C# parlance, class members of the format <access> <type> <name> { get; set; } are referred to as "properties" rather than "attributes." "Attributes," on the other hand, are the C# implementation of annotations, such as the custom attributes to which you are referring.

That said, I currently understand you to mean you have an automatically generated class with multiple properties. You would like each of these properties to have their own custom attributes, but if you edit the class they are removed the next time it is generated, and you cannot get the class generator to include custom attributes.

It might be helpful to know more of the context of your class. How is it being generated, for example? If it is an Entity Framework class, the following SO question may provide some insight:
Add data annotations to a class generated by entity framework. In general, is (or can you make) the generated class partial? If so, then you can still follow the approach in the above question's answer, viz. make your own partial class implementation that provides the properties' custom attributes.

For example, if your generated class looks (or can be made to look) like this:

/// <auto-generated />
public partial class MyClass
{
public int Num1 { get; set; }

public int Num2 { get; set; }

public string Str1 { get; set; }

public string Str2 { get; set; }
}

You could write the other part of the partial class with the custom annotations, like this:

/// human generated
public partial class MyClass
{
[Submit]
public int Num1 { get; set; }

[Submit]
public int Num2 { get; set; }

[Submit]
public string Str1 { get; set; }

[Submit]
public string Str2 { get; set; }
}

Again, without knowing more about your situation, I am not certain if this provides you the information you need, but I hope it at least gives you a starting point.

Edit

If the class is not partial, you might consider wrapping your generated class with a class whose wrapping properties use the custom attribute. For example,

/// human generated
public class MyClassWrapper
{
private readonly MyClass wrapped;

public MyClassWrapper(MyClass wrapped)
{
this.wrapped = wrapped;
}

[Submit]
public int Num1 { get => this.wrapped.Num1; set => this.wrapped.Num1 = value; }

[Submit]
public int Num2 { get => this.wrapped.Num2; set => this.wrapped.Num2 = value; }

[Submit]
public string Str1 { get => this.wrapped.Str1; set => this.wrapped.Str1 = value; }

[Submit]
public string Str2 { get => this.wrapped.Str2; set => this.wrapped.Str2 = value; }
}

Edit 2

If you would rather have a more dynamic solution, at the cost of some design and runtime complexity, you might consider this SO question: How to add property-level Attribute to the TypeDescriptor at runtime?. It seems to address a similar concern --

Really, it's for MS's Application Settings that generates code, so you can't extend it in any way property-wise.

I won't duplicate Gman's explanation entirely here, but essentially this approach consists of

  1. Get the type (MyClass) or an instance of the type myObject
  2. Use TypeDescriptor.GetProvider(MyClass/myObject).GetTypeDescriptor(MyClass/myObject) to get the type or object's baseline ICustomTypeDescriptor
  3. Construct his PropertyOverridingTypeDescriptor with this baseline descriptor
  4. Iterate through MyClass/myObject's properties' definitions with TypeDescriptor.GetProperties(MyClass/myObject). Use TypeDescriptor.CreateProperty to create a new property definition based on the current property's definition, that adds the custom attribute EditorAttribute (or in your case SubmitAttribute), and use the PropertyOverridingTypeDescriptor constructed in 3. to use the new property definition.
  5. Construct his TypeDescriptorOverridingProvider with the PropertyOverridingTypeDescriptor constructed in 3.
  6. Apply the new property definitions to MyClass/myObject with TypeDescriptor.AddProvider

How to add custom Visible property that doesn't hide the control to PropertyGrid?

The basic idea here is to have a shadow property which doesn't belong to the original object, but is showing in the PropertyGrid. Such a property can belong to the proxy class itself.

The following proxy class will hide the original Visible property, however it shows a Visible property which you can change but will not change the visibility of the original object:

enter image description here

Here's the code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
public class CustomObjectWrapper : CustomTypeDescriptor
{
public object WrappedObject { get; private set; }
public List<string> BrowsableProperties { get; private set; }
public CustomObjectWrapper(object o, List<string> pList)
: base(TypeDescriptor.GetProvider(o).GetTypeDescriptor(o))
{
WrappedObject = o;
BrowsableProperties = pList;
}
public override PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(new Attribute[] { });
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = base.GetProperties(attributes).Cast<PropertyDescriptor>()
.Where(p => p.Name != "Visible")
.Where(p => BrowsableProperties.Contains(p.Name))
.Select(p => TypeDescriptor.CreateProperty(
WrappedObject.GetType(),
p,
p.Attributes.Cast<Attribute>().ToArray()))
.ToList();
if (BrowsableProperties.Contains("Visible"))
{
var p = TypeDescriptor.GetProperties(this, true)["Visible"];
properties.Add(TypeDescriptor.CreateProperty(
this.GetType(), p, new[] { BrowsableAttribute.Yes }));
}
return new PropertyDescriptorCollection(properties.ToArray());
}
public bool Visible { get; set; }
public override object GetPropertyOwner(PropertyDescriptor pd)
{
if (pd == null)
return base.GetPropertyOwner(pd);
else if (pd.Name == "Visible")
return this;
else
return WrappedObject;
}
}


Related Topics



Leave a reply



Submit