Изменить параметр атрибута во время выполнения

Я не уверен, можно ли изменить параметр атрибута во время выполнения? Например, внутри сборки у меня есть следующий class

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

Это class, который предоставляется сторонним поставщиком, и я не могу изменить код . Но теперь я обнаружил, что приведенные выше описания неточны, и я хочу изменить имя категории «изменить меня» на что-то еще, когда я привязываю экземпляр вышеуказанного classа к сетке свойств.

Могу ли я узнать, как это сделать?

Ну, вы каждый день узнаете что-то новое, видимо, я солгал:

Что вообще не реализуется, так это то, что вы можете легко изменять значения экземпляра атрибута во время выполнения. Разумеется, причина в том, что экземпляры созданных classов атрибутов являются совершенно нормальными объектами и могут использоваться без ограничений. Например, мы можем получить объект:

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

… изменить значение своей переменной public и показать, что она изменилась:

 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

В случае, если кто-то еще спустится по этому пути, ответ – вы можете сделать это с reflectionм, кроме того, что не можете, потому что в структуре есть ошибка. Вот как вы это сделаете:

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

Все хорошо и хорошо, за исключением того, что атрибут категории изменяется для всех свойств, а не только «Возраст».

Вы можете легко подclassифицировать большинство общих атрибутов для обеспечения этой расширяемости:

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

Существуют более сложные варианты, которые include в себя запись пользовательских PropertyDescriptor s, открытых через TypeConverter , ICustomTypeDescriptor или TypeDescriptionProvider но это обычно является излишним.

К сожалению, атрибуты не предназначены для изменения во время выполнения. У вас в основном есть два варианта:

  1. Создайте аналогичный тип на лету, используя System.Reflection.Emit как показано ниже.

  2. Попросите вашего продавца добавить эту функциональность. Если вы используете 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().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; } } 

Вы решили проблему?

Ниже приведены возможные шаги для достижения приемлемого решения.

  1. Попробуйте создать дочерний class, переопределите все свойства, необходимые для изменения атрибута [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: эта часть решения не работает, если у вас есть большое количество свойств, которые могут потребоваться для перезаписи атрибутов. Это место, где встает вторая часть:

  1. Конечно, это не поможет, если class не наследуется, или если у вас много объектов (и свойств). Вам нужно будет создать полный автоматический прокси-class, который получит ваш class и создаст динамический class, применит атрибуты и, конечно же, сделает связь между двумя classами. Это немного сложнее, но также достижимо. Просто используйте reflection, и вы на правильном пути.

Учитывая, что выбранным элементом 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».

Я действительно так не думаю, если нет какого-то фанкового отражения, которое может оттянуть его. Имущественные украшения устанавливаются во время компиляции, и, насколько мне известно, исправлены

В то же время я пришел к частичному решению, полученному из следующих статей:

  1. ICustomTypeDescriptor, часть 1
  2. ICustomTypeDescriptor, часть 2
  3. Добавить (Удалить) Элементы в (из) PropertyGrid при Runtime

В принципе, вы создали бы общий class CustomTypeDescriptorWithResources , который получит свойства через reflection и загрузку Description и Category из файла (я полагаю, вам нужно отобразить локализованный текст, чтобы вы могли использовать файл ресурсов ( .resx ))

Вот «обманывающий» способ сделать это:

Если у вас есть фиксированное количество постоянных потенциальных значений для параметра атрибута, вы можете определить отдельное свойство для каждого потенциального значения параметра (и дать каждому свойству немного отличающийся атрибут), а затем переключить свойство, которое вы ссылаетесь динамически.

В VB.NET это может выглядеть так:

 Property Time As Date  ReadOnly Property TimeMonthly As Date Get Return Time End Get End Property  ReadOnly Property TimeQuarterly As Date Get Return Time End Get End Property  ReadOnly Property TimeYearly As Date Get Return Time End Get End Property 

Вы можете изменить значения атрибута во время выполнения на уровне classа (а не на объекте):

 var attr = TypeDescriptor.GetProperties(typeof(UserContact))["UserName"].Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute; attr.GetType().GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(attr, username_readonly); 
  • Как вызвать общий метод с заданным объектом типа?
  • Java: доступ к частному конструктору с параметрами типа
  • Как получить текущее имя свойства через reflection?
  • Как работать с varargs и reflection
  • Как загрузить сборку в AppDomain со всеми ссылками рекурсивно?
  • Java getMethod с параметром подclassа
  • Извлечение имени вызывающего метода из метода
  • GetEntryAssembly для веб-приложений
  • Как получить доступ к внутреннему classу с помощью Reflection
  • Рекурсивно Получить свойства и дочерние свойства classа
  • Прочитать значение атрибута метода
  • Interesting Posts

    Доступ к файлам, зашифрованным EFS, после сброса пароля Windows

    Удалите расширения .php с .htaccess без нарушения DirectoryIndex

    css3 анимация on: hover; заставить всю анимацию

    Почему такие вещи, как терминал Gnome, называются терминальными эмуляторами вместо «терминалов»?

    Как работает BreakIterator в Android?

    Эмулятор Android не может запускаться, из-за неправильной папки

    Что делает «задержка запуска» в типе запуска для службы Windows?

    Ширина столбца выходной мощности Powershell

    Толщина границы контрольной точки в ggplot

    Как распечатать уникальные элементы в массиве Perl?

    Имеет ли XAML условную директиву компилятора для режима отладки?

    Поддерживает ли Android-эмулятор OpenGL ES 2.0?

    Обнаружение перенаправления в jQuery $ .ajax?

    Выполнение скрипта PowerShell через контекстное меню проводника по элементам, содержащим амперсанды в их именах

    В Gradle, как объявить общие зависимости в одном месте?

    Давайте будем гением компьютера.