Change attribute parameter at runtime

I'm not sure if it is possible to change the attribute parameter at runtime? For example, inside the assembly I have the following class

public class UserInfo { [Category("change me!")] public int Age { get; set; } [Category("change me!")] public string Name { get; set; } } 

This is a class provided by a third-party provider, and I cannot change the code . But now I find that the above descriptions are inaccurate, and I want to change the name of the category "change me" to something else when I bind an instance of the specified class to the property grid.

Can I find out how to do this?

+57
reflection c # attributes
Sep 09 '08 at 5:55
source share
10 answers

Well, you learn something new every day, apparently I lied:

What is not implemented at all is that you can fairly change the value of the instance attribute easily at runtime. The reason is because of course, that the cases The created attribute classes are completely normal objects and can be used without restriction. For example, we can get an object:

 ASCII[] attrs1=(ASCII[]) typeof(MyClass).GetCustomAttributes(typeof(ASCII), false); 

... change the value of your public variable and show that it has changed:

 attrs1[0].MyData="A New String"; MessageBox.Show(attrs1[0].MyData); 

... and finally create another instance and show that its value has not changed:

 ASCII[] attrs3=(ASCII[]) typeof(MyClass).GetCustomAttributes(typeof(ASCII), false); MessageBox.Show(attrs3[0].MyData); 

http://www.vsj.co.uk/articles/display.asp?id=713

+24
Sep 09 '08 at 6:10
source share
β€” -

If someone else goes down this path, the answer will be that you can do it, with a reflection, except that you cannot, because there is a mistake in the framework. Here's how you do it:

  Dim prop As PropertyDescriptor = TypeDescriptor.GetProperties(GetType(UserInfo))("Age") Dim att As CategoryAttribute = DirectCast(prop.Attributes(GetType(CategoryAttribute)), CategoryAttribute) Dim cat As FieldInfo = att.GetType.GetField("categoryValue", BindingFlags.NonPublic Or BindingFlags.Instance) cat.SetValue(att, "A better description") 

Everything is good and good, except that the category attribute changes for all properties, not just Age.

+7
Jan 22 '10 at 0:21
source share

You can easily subclass most common attributes to provide this extensibility:

 using System; using System.ComponentModel; using System.Windows.Forms; class MyCategoryAttribute : CategoryAttribute { public MyCategoryAttribute(string categoryKey) : base(categoryKey) { } protected override string GetLocalizedString(string value) { return "Whad'ya know? " + value; } } class Person { [MyCategory("Personal"), DisplayName("Date of Birth")] public DateTime DateOfBirth { get; set; } } static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.Run(new Form { Controls = { new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = new Person { DateOfBirth = DateTime.Today} }}}); } } 

There are more complex options that include creating custom PropertyDescriptor s opened through TypeConverter , ICustomTypeDescriptor or TypeDescriptionProvider - but this is usually overkill.

+4
Dec 03 '09 at 12:36
source share

Unfortunately, attributes are not intended to be changed at runtime. You basically have two options:

  • Create a similar type on the fly using System.Reflection.Emit , as shown below.

  • Ask your seller to add this feature. If you use Xceed.WpfToolkit.Extended, you can download the source code here and easily implement an interface like IResolveCategoryName that would allow the attribute at runtime. I did a bit more, it was pretty easy to add more functionality, such as limitations when editing a numeric value in DoubleUpDown inside a PropertyGrid , etc.

     namespace Xceed.Wpf.Toolkit.PropertyGrid { public interface IPropertyDescription { double MinimumFor(string propertyName); double MaximumFor(string propertyName); double IncrementFor(string propertyName); int DisplayOrderFor(string propertyName); string DisplayNameFor(string propertyName); string DescriptionFor(string propertyName); bool IsReadOnlyFor(string propertyName); } } 

For the first option: this, however, is the lack of proper property binding to reflect the result back to the actual editable object.

  private static void CreatePropertyAttribute(PropertyBuilder propertyBuilder, Type attributeType, Array parameterValues) { var parameterTypes = (from object t in parameterValues select t.GetType()).ToArray(); ConstructorInfo propertyAttributeInfo = typeof(RangeAttribute).GetConstructor(parameterTypes); if (propertyAttributeInfo != null) { var customAttributeBuilder = new CustomAttributeBuilder(propertyAttributeInfo, parameterValues.Cast<object>().ToArray()); propertyBuilder.SetCustomAttribute(customAttributeBuilder); } } private static PropertyBuilder CreateAutomaticProperty(TypeBuilder typeBuilder, PropertyInfo propertyInfo) { string propertyName = propertyInfo.Name; Type propertyType = propertyInfo.PropertyType; // Generate a private field FieldBuilder field = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); // Generate a public property PropertyBuilder property = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, null); // The property set and property get methods require a special set of attributes: const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.HideBySig; // Define the "get" accessor method for current private field. MethodBuilder currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, getSetAttr, propertyType, Type.EmptyTypes); // Intermediate Language stuff... ILGenerator currGetIl = currGetPropMthdBldr.GetILGenerator(); currGetIl.Emit(OpCodes.Ldarg_0); currGetIl.Emit(OpCodes.Ldfld, field); currGetIl.Emit(OpCodes.Ret); // Define the "set" accessor method for current private field. MethodBuilder currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, getSetAttr, null, new[] { propertyType }); // Again some Intermediate Language stuff... ILGenerator currSetIl = currSetPropMthdBldr.GetILGenerator(); currSetIl.Emit(OpCodes.Ldarg_0); currSetIl.Emit(OpCodes.Ldarg_1); currSetIl.Emit(OpCodes.Stfld, field); currSetIl.Emit(OpCodes.Ret); // Last, we must map the two methods created above to our PropertyBuilder to // their corresponding behaviors, "get" and "set" respectively. property.SetGetMethod(currGetPropMthdBldr); property.SetSetMethod(currSetPropMthdBldr); return property; } public static object EditingObject(object obj) { // Create the typeBuilder AssemblyName assembly = new AssemblyName("EditingWrapper"); AppDomain appDomain = System.Threading.Thread.GetDomain(); AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.Run); ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assembly.Name); // Create the class TypeBuilder typeBuilder = moduleBuilder.DefineType("EditingWrapper", TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit, typeof(System.Object)); Type objType = obj.GetType(); foreach (var propertyInfo in objType.GetProperties()) { string propertyName = propertyInfo.Name; Type propertyType = propertyInfo.PropertyType; // Create an automatic property PropertyBuilder propertyBuilder = CreateAutomaticProperty(typeBuilder, propertyInfo); // Set Range attribute CreatePropertyAttribute(propertyBuilder, typeof(Category), new[]{"My new category value"}); } // Generate our type Type generetedType = typeBuilder.CreateType(); // Now we have our type. Let create an instance from it: object generetedObject = Activator.CreateInstance(generetedType); return generetedObject; } } 
+3
Aug 11 '14 at 23:35
source share

Did you solve the problem?

The following are possible steps to reach an acceptable solution.

  • Try to create a child class, override all the properties necessary to change the [Category] attribute (mark them new ). Example:
 public class UserInfo { [Category("Must change")] public string Name { get; set; } } public class NewUserInfo : UserInfo { public NewUserInfo(UserInfo user) { // transfer all the properties from user to current object } [Category("Changed")] public new string Name { get {return base.Name; } set { base.Name = value; } } public static NewUserInfo GetNewUser(UserInfo user) { return NewUserInfo(user); } } void YourProgram() { UserInfo user = new UserInfo(); ... // Bind propertygrid to object grid.DataObject = NewUserInfo.GetNewUser(user); ... } 

Edit later: This part of the solution does not work if you have a large number of properties that may be required to rewrite attributes. This is where the second part gets up:

  1. Of course, this will not help if the class is not inherited, or if you have many objects (and properties). You will need to create a full automatic proxy class that will receive your class and create a dynamic class, apply the attributes and, of course, make the connection between the two classes. This is a little trickier, but also achievable. Just use reflection and you're on the right track.
+1
Nov 09 '08 at 17:07
source share

Given that the selected PropertyGrid is Age:

 SetCategoryLabelViaReflection(MyPropertyGrid.SelectedGridItem.Parent, MyPropertyGrid.SelectedGridItem.Parent.Label, "New Category Label"); 

Where SetCategoryLabelViaReflection() is defined as follows:

 private void SetCategoryLabelViaReflection(GridItem category, string oldCategoryName, string newCategoryName) { try { Type t = category.GetType(); FieldInfo f = t.GetField("name", BindingFlags.NonPublic | BindingFlags.Instance); if (f.GetValue(category).Equals(oldCategoryName)) { f.SetValue(category, newCategoryName); } } catch (Exception ex) { System.Diagnostics.Trace.Write("Failed Renaming Category: " + ex.ToString()); } } 

As for the software installation of the selected item whose parent category you want to change; There are a number of simple solutions. Google "Set focus to a specific PropertyGrid."

+1
May 10 '12 at 20:16
source share

I really don’t think so unless there is some frightened reflection that can distract him. Property decorations are set at compile time and, as far as I know, have been fixed.

0
Sep 09 '08 at 6:05
source share

At the same time, I came up with a partial solution, obtained from the following articles:

Basically, you would create a generic class CustomTypeDescriptorWithResources<T> that would get the properties through reflection and loading the Description and Category from the file (I suppose you need to display localized text so you can use the resource file ( .resx ))

0
Jul 20 '09 at 10:59
source share

You can change attribute values ​​at run time at the class level (not an object):

 var attr = TypeDescriptor.GetProperties(typeof(UserContact))["UserName"].Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute; attr.GetType().GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(attr, username_readonly); 
0
Nov 05 '13 at 11:29
source share

Here is a "deceiving" way to do this:

If you have a fixed number of constant potential values ​​for an attribute parameter, you can define a separate property for each potential parameter value (and give each property a slightly different attribute), and then switch which property you are referring dynamically.

In VB.NET, this might look like this:

 Property Time As Date <Display(Name:="Month")> ReadOnly Property TimeMonthly As Date Get Return Time End Get End Property <Display(Name:="Quarter")> ReadOnly Property TimeQuarterly As Date Get Return Time End Get End Property <Display(Name:="Year")> ReadOnly Property TimeYearly As Date Get Return Time End Get End Property 
0
Jan 16 '17 at 15:39
source share



All Articles