UFO ET IT

런타임에 속성의 매개 변수 변경

ufoet 2020. 11. 18. 21:50
반응형

런타임에 속성의 매개 변수 변경


런타임 중에 속성의 매개 변수를 변경할 수 있는지 확실하지 않습니다. 예를 들어, 어셈블리 내부에는 다음 클래스가 있습니다.

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

이것은 타사 공급 업체에서 제공하는 클래스이며 코드를 변경할 수 없습니다 . 하지만 지금은 위의 설명이 정확하지 않다는 것을 알았고, 위 클래스의 인스턴스를 속성 그리드에 바인딩 할 때 "change me"범주 이름을 다른 것으로 변경하고 싶습니다.

이 작업을 수행하는 방법을 알 수 있습니까?


당신은 매일 새로운 것을 배웁니다. 분명히 거짓말을했습니다.

일반적으로 인식되지 않는 것은 런타임에 속성 인스턴스 값을 상당히 쉽게 변경할 수 있다는 것 입니다. 그 이유는 물론 생성 된 속성 클래스의 인스턴스가 완벽하게 정상적인 객체이고 제한없이 사용할 수 있기 때문입니다. 예를 들어, 우리는 객체를 얻을 수 있습니다 :

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


다른 사람이이 길을 걸어가는 경우, 프레임 워크에 버그가 있기 때문에 할 수 없다는 점을 제외하고는 반사를 통해 할 수 있습니다. 방법은 다음과 같습니다.

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

카테고리 속성이 '연령'뿐만 아니라 모든 속성에 대해 변경된다는 점을 제외하면 모두 훌륭합니다.


이러한 확장 성을 제공하기 위해 대부분의 공통 속성을 매우 쉽게 하위 클래스화할 수 있습니다.

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-하지만 과잉 보통이다.


불행히도 속성은 런타임에 변경되지 않습니다. 기본적으로 두 가지 옵션이 있습니다.

  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. 자식 클래스를 만들고 [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);

...

}

나중에 편집 : 속성을 다시 작성해야 할 수있는 속성이 많은 경우 솔루션의이 부분을 사용할 수 없습니다. 여기에서 2 부가 시작됩니다.

  1. Of course, this won't help if the class is not inheritable, or if you have a lot of objects (and properties). You would need to create a full automatic proxy class that gets your class and creates a dynamic class, applies attributes and of course makes a connection between the two classes.. This is a little more complicated, but also achievable. Just use reflection and you're on the right road.

Given that the PropertyGrid's selected item is "Age":

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

Where SetCategoryLabelViaReflection() is defined as follows:

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

As far as programatically setting the selected item, the parent category of which you wish to change; there are a number of simple solutions. Google "Set Focus to a specific PropertyGrid property".


I really don't think so, unless there's some funky reflection that can pull it off. The property decorations are set at compile time and to my knowledge are fixed


In the mean time I've come to a partial solution, derived from the following articles:

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

Basically you would create a generic class CustomTypeDescriptorWithResources<T>, that would get the properties through reflection and load Description and Category from a file (I suppose you need to display localized text so you could use a resources file (.resx))


Here's a "cheaty" way to do it:

If you have a fixed number of constant potential values for the attribute parameter, you can define a separate property for each potential value of the parameter (and give each property that slightly different attribute), then switch which property you reference dynamically.

In VB.NET, it might look like this:

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

You can change Attribute values at runtime at Class level (not object):

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

참고URL : https://stackoverflow.com/questions/51269/change-attributes-parameter-at-runtime

반응형