2008-09-09 41 views
57

我不确定是否可以在运行时更改属性的参数?例如,一个组件内部我有下面的类在运行时更改属性的参数

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

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

我可以知道如何做到这一点吗?

回答

0

我真的不这么认为,除非有一些时髦的反射可以把它关掉。酒店装饰在编译时间,据我所知,固定

设置
24

那么你每天都学到新的东西,显然我说谎:

什么是不普遍认识是, 你可以改变属性实例值在运行时很容易 。原因是, 当然,创建的 属性类的实例是完全正常的对象,可以是 而不受限制。例如, 我们可以得到对象:

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

+11

嗯,不是真的。您可以创建属性对象的实例并对其进行修改,但不会影响使用属性上标记的属性的任何内容(因为它们将获得自己的未更改的实例)。 – denver 2014-07-23 18:04:19

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. 当然,如果类不是可继承的,或者如果您有很多对象(和属性) 。您需要创建一个完整的自动代理类来获取您的类并创建一个动态类,应用属性,并且当然会在两个类之间建立连接。这有点复杂,但也可以实现。只需使用反射,你就走在了正确的道路上。
+0

Bogdan, 恐怕对于班级进行分类和做所有的定义是不切实际的,至少可以说。 – Graviton 2008-11-11 02:07:55

+0

如果您是继承类,那么您将不得不自动重新创建所有属性,并替换旧的属性。如果你可以继承子类,这是最简单的解决方案。主要想法是自动创建一个代理(使用动态类型),并在运行中替换属性。 – 2008-11-11 07:38:08

4

你也可以继承最常见的属性很容易提供这种可扩展性:

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暴露的较为复杂的选项 - 但通常是矫枉过正。

+0

但马克,他说他无法访问代码 – toddmo 2016-11-23 20:58:31

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

一切都很好,除了类别属性被更改所有属性,而不仅仅是“时代”。

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

至于编程设定所选择的项目,父类,其中你想改变;有一些简单的解决方案。 Google“将焦点设置为特定的PropertyGrid属性”。

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

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

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

  2. 请求供应商添加此功能。如果您使用的是Xceed.WpfToolkit.Extended,您可以从here下载源代码,并轻松实现可在运行时解析属性的界面,如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; 
    } 
} 
0

这里有一个“cheaty”的方式做到这一点:

如果你有一个属性参数恒电位值的一个固定的数字,你可以定义一个单独的属性参数的每个潜在价值(并给每个属性稍微不同的属性),然后切换您动态引用的属性。

在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