Изменить параметр атрибута во время выполнения - PullRequest
62 голосов
/ 09 сентября 2008

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

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

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

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

Ответы [ 10 ]

27 голосов
/ 09 сентября 2008

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

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

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

7 голосов
/ 22 января 2010

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

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 голосов
/ 03 декабря 2009

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

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 - но обычно это излишне.

2 голосов
/ 12 августа 2014

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

  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<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;
    }
}
1 голос
/ 11 мая 2012

Учитывая, что выбранным элементом 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 голос
/ 09 ноября 2008

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

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

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

...

}

Позднее редактирование: Эта часть решения не работает, если у вас есть большое количество свойств, которые могут понадобиться для перезаписи атрибутов. Здесь начинается вторая часть:

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

Вот «хитрый» способ сделать это:

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

В 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
0 голосов
/ 05 ноября 2013

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

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 голосов
/ 20 июля 2009

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

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

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

0 голосов
/ 09 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...