69

ランタイム中に属性のパラメーターを変更できるかどうかわかりません。たとえば、アセンブリ内に次のクラスがあります

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

これはサード パーティ ベンダーによって提供されるクラスであり、コードを変更することはできません。しかし、上記の説明は正確ではないことがわかりました。上記のクラスのインスタンスをプロパティ グリッドにバインドするときに、"change me" カテゴリ名を別の名前に変更したいと考えています。

これを行う方法を教えてください。

4

10 に答える 10

29

そうですね、あなたは毎日何か新しいことを学んでいます、どうやら私は嘘をつきました:

一般に認識されていないのは、実行時に属性インスタンスの値をかなり簡単に変更できるということです。その理由はもちろん、作成される属性クラスのインスタンスは完全に通常のオブジェクトであり、制限なく使用できるからです。たとえば、次のオブジェクトを取得できます。

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

于 2008-09-09T06:10:53.433 に答える
7

他の誰かがこの道を歩いている場合、答えは、フレームワークにバグがあるためにできないことを除いて、リフレクションでそれを行うことができるということです。これを行う方法は次のとおりです。

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

「年齢」だけでなく、すべてのプロパティのカテゴリ属性が変更されていることを除いて、すべて順調です。

于 2010-01-22T00:21:27.300 に答える
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 の記述を含む、より複雑なオプションがありますが、通常はやり過ぎです。TypeConverterICustomTypeDescriptorTypeDescriptionProvider

于 2009-12-03T12:36:27.147 に答える
2

残念ながら、属性は実行時に変更されることを意図していません。基本的に次の 2 つのオプションがあります。

  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;
    }
}
于 2014-08-11T23:35:28.603 に答える
1

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

于 2012-05-10T20:16:40.757 に答える
1

問題は解決しましたか?

許容できる解決策を達成するための可能な手順を次に示します。

  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. もちろん、クラスが継承可能でない場合や、多くのオブジェクト (およびプロパティ) がある場合、これは役に立ちません。クラスを取得して動的クラスを作成し、属性を適用し、もちろん2つのクラス間の接続を作成する完全自動プロキシクラスを作成する必要があります..これはもう少し複雑ですが、達成可能です. リフレクションを使用するだけで、正しい道を進んでいます。
于 2008-11-09T17:07:37.480 に答える
0

それまでの間、次の記事から派生した部分的な解決策にたどり着きました。

  1. ICustomTypeDescriptor、パート 1
  2. ICustomTypeDescriptor、パート 2
  3. 実行時に PropertyGrid に (から) 項目を追加 (削除)

CustomTypeDescriptorWithResources<T>基本的には、リフレクションとロードを介しDescriptionてファイルからプロパティを取得するジェネリック クラスを作成しCategoryます (リソース ファイルを使用できるように、ローカライズされたテキストを表示する必要があると思います ( .resx)) 。

于 2009-07-20T10:59:33.210 に答える
0

それをやってのけることができるファンキーな反射がない限り、私は本当にそうは思わない. プロパティの装飾はコンパイル時に設定され、私の知る限り修正されています

于 2008-09-09T06:05:37.627 に答える
-1

クラス レベル (オブジェクトではない) で実行時に属性値を変更できます。

var attr = TypeDescriptor.GetProperties(typeof(UserContact))["UserName"].Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute;
attr.GetType().GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(attr, username_readonly);
于 2013-11-05T11:29:49.227 に答える