Изменить параметр атрибута во время выполнения
Я не уверен, возможно ли изменить параметр атрибута во время выполнения? Например, внутри сборки у меня есть следующий класс
public class UserInfo
{
[Category("change me!")]
public int Age
{
get;
set;
}
[Category("change me!")]
public string Name
{
get;
set;
}
}
Это класс, предоставляемый сторонним поставщиком, и Я не могу изменить код. Но теперь я обнаружил, что приведенные выше описания неточны, и я хочу изменить название категории "изменить меня" на что-то еще, когда я привязываю экземпляр указанного класса к сетке свойств.
Могу ли я узнать, как это сделать?
Ответы
Ответ 1
Ну, ты узнаешь что-то новое каждый день, видимо, я солгал:
Что вообще не реализуется, так это то, что вы можете справедливо изменить значения атрибута instanceлегко во время выполнения. Причина в том, что конечно, что случаи Созданные классы атрибутов совершенно нормальные объекты и могут быть используется без ограничений. Например, мы можем получить объект:
ASCII[] attrs1=(ASCII[])
typeof(MyClass).GetCustomAttributes(typeof(ASCII), false);
... измените значение своей общедоступной переменной и покажите, что она изменилась:
attrs1[0].MyData="A New String";
MessageBox.Show(attrs1[0].MyData);
... и, наконец, создать другой экземпляр и покажите, что его значение не изменилось:
ASCII[] attrs3=(ASCII[])
typeof(MyClass).GetCustomAttributes(typeof(ASCII), false);
MessageBox.Show(attrs3[0].MyData);
http://www.vsj.co.uk/articles/display.asp?id=713
Ответ 2
Если кто-то еще спустится по этому пути, ответ будет заключаться в том, что вы можете это сделать, с отражением, за исключением того, что вы не можете, потому что есть ошибка в рамках. Вот как вы это сделаете:
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")
Все хорошо и хорошо, за исключением того, что атрибут категории изменяется для всех свойств, а не только "Возраст".
Ответ 3
Вы можете легко подклассифицировать большинство общих атрибутов для обеспечения этой расширяемости:
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}
}}});
}
}
Есть более сложные параметры, которые включают в себя создание пользовательских PropertyDescriptor
s, открытых через TypeConverter
, ICustomTypeDescriptor
или TypeDescriptionProvider
- но это обычно перебор.
Ответ 4
К сожалению, атрибуты не предназначены для изменения во время выполнения. У вас в основном есть два варианта:
-
Создайте аналогичный тип на лету, используя System.Reflection.Emit
, как показано ниже.
-
Попросите вашего продавца добавить эту функцию. Если вы используете Xceed.WpfToolkit.Extended, вы можете загрузить исходный код здесь и легко реализовать интерфейс типа IResolveCategoryName
, который разрешил бы атрибут во время выполнения. Я сделал немного больше, было довольно легко добавить больше функциональных возможностей, таких как ограничения при редактировании числового значения в DoubleUpDown
внутри PropertyGrid
и т.д.
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);
}
}
Для первого варианта: это, однако, отсутствие надлежащего связывания свойств, чтобы отразить результат обратно к фактическому редактируемому объекту.
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;
}
}
Ответ 5
Вы решили проблему?
Ниже приведены возможные шаги для достижения приемлемого решения.
- Попробуйте создать дочерний класс, переопределите все свойства, необходимые для изменения атрибута
[Category]
(пометьте их new
). Пример:
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: Эта часть решения не работает, если у вас есть большое количество свойств, которые могут потребоваться для перезаписи атрибутов. Вот где встает вторая часть:
- Конечно, это не поможет, если класс не наследуется, или если у вас много объектов (и свойств). Вам нужно будет создать полный автоматический прокси-класс, который получит ваш класс и создаст динамический класс, применит атрибуты и, конечно же, сделает связь между двумя классами. Это немного сложнее, но также достижимо. Просто используйте отражение, и вы на правильном пути.
Ответ 6
Учитывая, что выбранным элементом PropertyGrid является "Возраст":
SetCategoryLabelViaReflection(MyPropertyGrid.SelectedGridItem.Parent,
MyPropertyGrid.SelectedGridItem.Parent.Label, "New Category Label");
Где SetCategoryLabelViaReflection()
определяется следующим образом:
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());
}
}
Что касается программной установки выбранного элемента, родительская категория которого вы хотите изменить; существует ряд простых решений. Google "Настроить фокус на определенное свойство PropertyGrid".
Ответ 7
Я действительно так не думаю, если не найдется какое-то напуганное отражение, которое может отвлечь его. Декорации свойств устанавливаются во время компиляции и, насколько мне известно, исправлены.
Ответ 8
В то же время я пришел к частичному решению, полученному из следующих статей:
В принципе, вы бы создали общий класс CustomTypeDescriptorWithResources<T>
, который получил бы свойства через отражение и загрузку Description
и Category
из файла (я полагаю, вам нужно отобразить локализованный текст, чтобы вы могли использовать файл ресурсов (.resx
))
Ответ 9
Вы можете изменить значения атрибута во время выполнения на уровне класса (не объект):
var attr = TypeDescriptor.GetProperties(typeof(UserContact))["UserName"].Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute;
attr.GetType().GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(attr, username_readonly);
Ответ 10
Вот "обманывающий" способ сделать это:
Если у вас есть фиксированное количество постоянных потенциальных значений для параметра атрибута, вы можете определить отдельное свойство для каждого потенциального значения параметра (и дать каждому свойству немного отличающийся атрибут), а затем переключить, какое свойство вы ссылаетесь динамически.
В VB.NET это может выглядеть так:
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