69

我不确定是否可以在运行时更改属性的参数?例如,在程序集中,我有以下类

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

这是由第三方供应商提供的类,我无法更改代码。但是现在我发现上面的描述并不准确,当我将上面类的实例绑定到属性网格时,我想将“更改我”类别名称更改为其他名称。

我可以知道该怎么做吗?

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

还有更复杂的选项涉及编写 custom PropertyDescriptor,通过或- 公开TypeConverter,但这通常是矫枉过正。ICustomTypeDescriptorTypeDescriptionProvider

于 2009-12-03T12:36:27.147 回答
2

不幸的是,属性并不意味着在运行时改变。你基本上有两个选择:

  1. System.Reflection.Emit使用如下所示重新创建一个类似的类型。

  2. 请您的供应商添加此功能。如果您使用的是 Xceed.WpfToolkit.Extended,您可以从这里下载源代码并轻松实现一个类似的接口,该接口IResolveCategoryName将在运行时解析属性。我做了更多的事情,在编辑 a 中的数值时添加更多功能(如限制)非常容易DoubleUpDownPropertyGrid等等。

    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

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

至于以编程方式设置所选项目,您希望更改其父类别;有许多简单的解决方案。谷歌“将焦点设置为特定的 PropertyGrid 属性”。

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

...

}

稍后编辑: 如果您有大量可能需要重写属性的属性,则此部分解决方案不可行。这是第二部分到位的地方:

  1. 当然,如果类不可继承,或者您有很多对象(和属性),这将无济于事。您需要创建一个全自动代理类来获取您的类并创建一个动态类,应用属性,当然还要在两个类之间建立连接。这有点复杂,但也是可以实现的。只需使用反射,您就走在了正确的道路上。
于 2008-11-09T17:07:37.480 回答
1

这是一种“作弊”的方法:

如果属性参数有固定数量的恒定潜在值,则可以为参数的每个潜在值定义一个单独的属性(并为每个属性赋予稍微不同的属性),然后动态切换您引用的属性。

在 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
于 2017-01-16T15:39:00.333 回答
0

我真的不这么认为,除非有一些时髦的反射可以把它拉下来。属性装饰是在编译时设置的,据我所知是固定的

于 2008-09-09T06:05:37.627 回答
0

同时,我得出了一个部分解决方案,源自以下文章:

  1. ICustomTypeDescriptor,第 1 部分
  2. ICustomTypeDescriptor,第 2 部分
  3. 在运行时向(从)PropertyGrid 添加(删除)项目

基本上,您将创建一个通用类CustomTypeDescriptorWithResources<T>,它将通过反射和加载Description以及Category从文件中获取属性(我想您需要显示本地化文本,以便可以使用资源文件(.resx))

于 2009-07-20T10:59:33.210 回答
-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 回答