c# - etiquetas - Cambiar el parámetro del atributo en el tiempo de ejecución
title ejemplos (10)
¿Has resuelto el problema?
Aquí hay posibles pasos para lograr una solución aceptable.
- Intente crear una clase secundaria, redefina todas las propiedades que necesita para cambiar el atributo
[Category]
(márquelas connew
). 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); ... }
Más tarde Edición: esta parte de la solución no es viable si tiene una gran cantidad de propiedades que podría necesitar para volver a escribir los atributos. Aquí es donde la segunda parte entra en su lugar:
- Por supuesto, esto no ayudará si la clase no es heredable, o si tiene muchos 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, haga 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.
No estoy seguro de si es posible cambiar los parámetros del atributo durante el tiempo de ejecución. Por ejemplo, dentro de una asamblea tengo la siguiente clase
public class UserInfo
{
[Category("change me!")]
public int Age
{
get;
set;
}
[Category("change me!")]
public string Name
{
get;
set;
}
}
Esta es una clase proporcionada por un proveedor externo y 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.
¿Puedo saber cómo hacer esto?
Aquí hay una manera "engañosa" de hacerlo:
Si tiene un número fijo de valores potenciales constantes para el parámetro de atributo, puede definir una propiedad separada para cada valor potencial del parámetro (y otorgar a cada propiedad ese atributo ligeramente diferente), luego cambiar a qué propiedad hace referencia de forma dinámica.
En VB.NET, podría verse así:
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
Bueno, aprendes algo nuevo todos los días, aparentemente mentí:
Lo que generalmente no se comprende es que puede cambiar valores de instancia de atributo bastante fácilmente en tiempo de ejecución. La razón es, por supuesto, que las instancias de las clases de atributos que se crean son objetos perfectamente normales y se pueden usar sin restricciones. Por ejemplo, podemos obtener el objeto:
ASCII[] attrs1=(ASCII[]) typeof(MyClass).GetCustomAttributes(typeof(ASCII), false);
... cambia el valor de su variable pública y muestra que ha cambiado:
attrs1[0].MyData="A New String"; MessageBox.Show(attrs1[0].MyData);
... y finalmente crea otra instancia y muestra que su valor no ha cambiado:
ASCII[] attrs3=(ASCII[]) typeof(MyClass).GetCustomAttributes(typeof(ASCII), false); MessageBox.Show(attrs3[0].MyData);
Dado que el elemento seleccionado de PropertyGrid es "Edad":
SetCategoryLabelViaReflection(MyPropertyGrid.SelectedGridItem.Parent,
MyPropertyGrid.SelectedGridItem.Parent.Label, "New Category Label");
Donde SetCategoryLabelViaReflection()
se define de la siguiente manera:
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());
}
}
En cuanto a establecer programáticamente el elemento seleccionado, la categoría principal de la cual desea cambiar; hay una serie de soluciones simples. Google "Establecer el foco en una propiedad PropertyGrid específica".
En caso de que alguien más camine por esta avenida, la respuesta es que puedes hacerlo, con reflexión, excepto que no puedes porque hay un error en el marco. Así es como lo harías:
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 va bien, excepto que el atributo de categoría se cambia para todas las propiedades, no solo para ''Edad''.
Lamentablemente, los atributos no están destinados a cambiar en el tiempo de ejecución. Básicamente tienes dos opciones:
Vuelva a crear un tipo similar sobre la marcha usando
System.Reflection.Emit
como se muestra a continuación.Pídale 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 enDoubleUpDown
dentro dePropertyGrid
, 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); } }
Para la primera opción: sin embargo, esta falta de unión de propiedad adecuada para reflejar el resultado al objeto real 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;
}
}
Mientras tanto, he llegado a una solución parcial, derivada de los siguientes artículos:
- ICustomTypeDescriptor, Parte 1
- ICustomTypeDescriptor, Parte 2
- Agregar (quitar) elementos a (desde) PropertyGrid en Runtime
Básicamente crearía una clase genérica CustomTypeDescriptorWithResources<T>
, que obtendría las propiedades a través de reflexión y cargaría Description
y Category
de un archivo (supongo que necesita mostrar texto localizado para poder usar un archivo de recursos ( .resx
))
Puede cambiar los valores de atributo en el tiempo de ejecución en el nivel de clase (no en el objeto):
var attr = TypeDescriptor.GetProperties(typeof(UserContact))["UserName"].Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute;
attr.GetType().GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(attr, username_readonly);
Puede subclasificar la 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 de PropertyDescriptor
s personalizados, expuestos a través de TypeConverter
, ICustomTypeDescriptor
o TypeDescriptionProvider
, pero eso suele ser exagerado.
Realmente no lo creo, a menos que haya algún reflejo funky que pueda llevarlo a cabo. Las decoraciones de la propiedad se configuran en tiempo de compilación y, a mi conocimiento, están corregidas