[C#] रनटाइम पर विशेषता का पैरामीटर बदलें


Answers

यदि कोई अन्य इस एवेन्यू को नीचे चलाता है, तो उत्तर है कि आप इसे प्रतिबिंब के साथ कर सकते हैं, सिवाय इसके कि आप नहीं कर सकते क्योंकि ढांचे में कोई बग है। यहां बताया गया है कि आप यह कैसे करेंगे:

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

सभी अच्छी तरह से और अच्छे, सिवाय इसके कि श्रेणी विशेषता सभी गुणों के लिए बदल जाती है, न सिर्फ 'आयु'।

Question

मुझे यकीन नहीं है कि क्या रनटाइम के दौरान विशेषता के पैरामीटर को बदलना संभव है? उदाहरण के लिए, एक विधानसभा के अंदर मुझे निम्नलिखित वर्ग है I

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

यह एक ऐसा वर्ग है जिसे तृतीय पक्ष विक्रेता द्वारा प्रदान किया गया है और मैं कोड को बदल नहीं सकता । लेकिन अब मुझे पता चला कि उपरोक्त विवरण सही नहीं हैं, और जब मैं उपरोक्त वर्ग की एक संपत्ति ग्रिड को बाँध दूँ, तो मैं "मुझे बदलने" श्रेणी का नाम बदलना चाहता हूं।

क्या मैं यह कैसे जानूं?




यह करने के लिए एक "धोखाधड़ी" तरीका है:

यदि आपके पास विशेषता पैरामीटर के लिए निरंतर संभावित मानों की निश्चित संख्या है, तो आप पैरामीटर के प्रत्येक संभावित मान के लिए एक अलग प्रॉपर्टी को परिभाषित कर सकते हैं (और प्रत्येक प्रॉपर्टी को थोड़ा भिन्न विशेषता दे सकते हैं), फिर उस प्रॉपर्टी को स्विच करें जो आप गतिशील रूप से संदर्भ करते हैं।

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



मतलब समय में मैं आंशिक समाधान के लिए आया हूं, जो निम्नलिखित लेखों से प्राप्त हुआ है:

  1. ICustomTypeDescriptor, भाग 1
  2. ICustomTypeDescriptor, भाग 2
  3. रनटाइम पर प्रॉपर्टीग्रिड (से) आइटम जोड़ें (निकालें)

असल में आप एक सामान्य श्रेणी CustomTypeDescriptorWithResources<T> , जो प्रतिबिंब के माध्यम से गुण मिलेगा और एक फ़ाइल से Description और Category लोड करेगा (मुझे लगता है आपको स्थानीय पाठ प्रदर्शित करने की आवश्यकता है ताकि आप एक संसाधन फ़ाइल ( .resx ) का उपयोग कर सकें)




यह देखते हुए कि संपत्ति ग्रिड का चयनित आइटम "आयु" है:

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 "विशिष्ट संपत्ति ग्रिड संपत्ति पर फोकस सेट करें"




दुर्भाग्यवश विशेषताओं का क्रम क्रम में बदलने के लिए नहीं है आपके पास मूल रूप से दो विकल्प हैं:

  1. प्रणाली का उपयोग करते हुए फ्लाई पर एक समान प्रकार को पुन: बनाएँ। System.Reflection.Emit नीचे दिखाए गए अनुसार।

  2. इस कार्यक्षमता को जोड़ने के लिए अपने विक्रेता से पूछें यदि आप Xceed.WpfToolkit.Extended का प्रयोग कर रहे हैं तो आप यहां से स्रोत कोड डाउनलोड कर सकते हैं और आसानी से IResolveCategoryName जैसी इंटरफ़ेस को कार्यान्वित कर सकते हैं जो रनटाइम पर विशेषता को हल करेंगे। मैंने उस से थोड़ी अधिक किया, PropertyGrid अंदर एक DoubleUpDown में एक संख्यात्मक मान संपादित करते समय सीमाओं की तरह अधिक कार्यक्षमता जोड़ना बहुत आसान था, आदि।

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