2008-09-09 19 views
57

No estoy seguro de si es posible cambiar los parámetros del atributo durante el tiempo de ejecución? Por ejemplo, dentro de un ensamblaje tengo la siguiente claseCambie el parámetro del atributo en el tiempo de ejecución

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

Esta es una clase que es proporcionada por un proveedor de terceros y que no puedo cambiar el código. Pero ahora descubrí que las descripciones anteriores no son precisas, y quiero cambiar el nombre de la categoría "cambiarme" por otra cosa cuando vinculo una instancia de la clase anterior a una cuadrícula de propiedades.

¿Sé cómo hacerlo?

Respuesta

0

Realmente no lo creo, a menos que haya algún reflejo funky que pueda llevarlo a cabo. Las decoraciones de propiedad se establecen en tiempo de compilación y que yo sepa son fijos

24

bien se aprende algo nuevo todos los días, al parecer mentí:

Lo que generalmente no se dio cuenta es que puede cambiar atributo instancia valores bastante fácilmente en tiempo de ejecución. La razón es, del curso , que las instancias de las clases de atributo que se crean son objetos perfectamente normales y pueden ser utilizadas sin restricción. Por ejemplo, podemos obtener el objeto:

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

... cambiar el valor de su variable pública y demostrar que ha cambiado:

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

... y, finalmente, crear otra instancia y demostrar que su valor es el mismo:

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

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

+11

Bueno, no realmente. Puede crear una instancia del objeto de atributo y modificarlo, pero no afectará nada usando los atributos marcados en la propiedad (ya que obtendrán su propia instancia sin cambios). – denver

1

¿Has resuelto el problema?

Aquí hay posibles pasos para lograr una solución aceptable.

  1. intenta crear una clase hija, redefinir todas las propiedades que necesita para cambiar el atributo [Category] (marcarlos con new). Ejemplo:
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); 

... 

} 

Posteriormente Editar:Esta parte de la solución no es viable si tiene un gran número de propiedades que pueda necesitar para volver a escribir los atributos. Aquí es donde viene la segunda parte en su lugar:

  1. Por supuesto, esto no ayuda si la clase no es heredable, o si tiene una gran cantidad de objetos (y propiedades) . Debería crear una clase proxy automática completa que obtenga su clase y cree una clase dinámica, aplique atributos y, por supuesto, establezca una conexión entre las dos clases.Esto es un poco más complicado, pero también alcanzable. Solo usa la reflexión y estás en el camino correcto.
+0

Bogdan, Me temo que subclasificar la clase y hacer toda la redefinición no es práctico, por decir lo menos. – Graviton

+0

Si está creando una subclase, deberá volver a crear automáticamente todos los atributos y reemplazar los atributos anteriores. Esta es la solución más fácil si puede subclase. La idea principal era crear un proxy automáticamente (utilizando un tipo dinámico) y reemplazar los atributos sobre la marcha. –

0

Por el momento he llegado a una solución parcial, derivado de los siguientes artículos:

  1. ICustomTypeDescriptor, Part 1
  2. ICustomTypeDescriptor, Part 2
  3. Add (Remove) Items to (from) PropertyGrid at Runtime

Básicamente, se crearía una clase genérica CustomTypeDescriptorWithResources<T>, que obtendría las propiedades a través de reflejar iones y la carga Description y Category de un archivo (supongo que necesita para mostrar texto localizado lo que podría utilizar un archivo de recursos (.resx))

4

Usted puede subclase mayoría de los atributos comunes con bastante facilidad para proporcionar esta extensibilidad:

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

Hay opciones más complejas que implican la escritura personalizados PropertyDescriptor s, expuestas a través TypeConverter, ICustomTypeDescriptor o TypeDescriptionProvider - pero eso es por lo general un exceso.

+0

Pero Marc, dijo que no tiene acceso al código – toddmo

7

En caso de que alguien más camine por esta avenida, la respuesta es que puede hacerlo, con reflejo, excepto que no puede porque hay un error en el marco. He aquí cómo lo haría:

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") 

Todo muy bien, excepto que el atributo categoría se cambia para todas las propiedades, no sólo 'edad'.

+4

No lo llamaría un error si se entromete en los campos "BindingFlags.NonPublic". –

+1

Creo que esto es de hecho un error porque también ocurre con las propiedades públicas, incluso si no utiliza los campos "BindingFlags.NonPublic". ¿Alguien sabe si esto se ha planteado? Un enlace a la página de error sería útil. ¡Usar TypeDescriptor en lugar de Reflection funcionó perfectamente! – kkara

1

Teniendo en cuenta que el elemento seleccionado del PropertyGrid es "Edad":

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

Dónde SetCategoryLabelViaReflection() se define como sigue:

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

Como establecer la medida de lo programáticamente el elemento seleccionado, la categoría padre de los cuales deseas cambiar; hay una serie de soluciones simples. Google "Establecer el foco en una propiedad PropertyGrid específica".

-1

Puede cambiar los valores de atributos en tiempo de ejecución a nivel de clase (no se opone):

var attr = TypeDescriptor.GetProperties(typeof(UserContact))["UserName"].Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute; 
attr.GetType().GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(attr, username_readonly); 
2

Desafortunadamente atributos no están destinados a cambiar en tiempo de ejecución. Es, básicamente, tiene dos opciones:

  1. recrear un tipo similar sobre la marcha utilizando System.Reflection.Emit como se muestra a continuación.

  2. Solicite a su proveedor que agregue esta funcionalidad. Si está utilizando Xceed.WpfToolkit.Extended, puede descargar el código fuente desde here e implementar fácilmente una interfaz como IResolveCategoryName que resolvería el atributo en tiempo de ejecución. Hice un poco más que eso, fue bastante fácil agregar más funcionalidades como límites al editar un valor numérico en un DoubleUpDown dentro del 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); 
        } 
    } 
    

Por primera opción: Esto, sin embargo la falta de unión a reflejar el resultado de nuevo al objeto real propiedad adecuada que se está editando.

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's create an instance from it: 
     object generetedObject = Activator.CreateInstance(generetedType); 

     return generetedObject; 
    } 
} 
0

Aquí está una manera "cheaty" para hacerlo:

Si usted tiene un número fijo de valores de potencial constante para el parámetro de atributo, se puede definir una propiedad separada para cada posible valor del parámetro (y otorgue a cada propiedad ese atributo ligeramente diferente), luego cambie a qué propiedad hace referencia de forma dinámica.

En VB.NET, que podría tener este aspecto:

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 
Cuestiones relacionadas