我不确定是否可以在运行时更改属性的参数?例如,在程序集内部,我有以下类
public class UserInfo
{
[Category("change me!")]
public int Age
{
get;
set;
}
[Category("change me!")]
public string Name
{
get;
set;
}
}
这是由第三方供应商提供的类,我无法更改代码。但是现在我发现上面的描述并不准确,我想在将上述类的实例绑定到属性网格时将“更改我”类别名称更改为其他名称。
我可以知道怎么做吗?
答案 0 :(得分:27)
你每天都学到新东西,显然我撒了谎:
通常没有意识到的是 您可以公平地更改属性实例值 很容易在运行时。原因是, 当然,那个例子 创建的属性类是 完全正常的物体,可以 使用不受限制。例如, 我们可以得到这个对象:
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);
答案 1 :(得分: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")
一切都很好,除了为所有属性更改了category属性,而不仅仅是'Age'。
答案 2 :(得分: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
,通过TypeConverter
,ICustomTypeDescriptor
或TypeDescriptionProvider
公开 - 但这通常是矫枉过正。
答案 3 :(得分:2)
不幸的是,属性并不意味着在运行时更改。你基本上有两个选择:
使用System.Reflection.Emit
动态重新创建类似的类型,如下所示。
要求您的供应商添加此功能。如果您使用的是Xceed.WpfToolkit.Extended,则可以从here下载源代码,并轻松实现可在运行时解析该属性的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;
}
}
答案 4 :(得分: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); ... }
稍后编辑: 如果您有可能需要重写属性的大量属性,则此部分解决方案无法使用。这是第二部分到位的地方:
答案 5 :(得分:1)
鉴于PropertyGrid的所选项目是&#34;年龄&#34;:
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&#34;将焦点设置为特定的PropertyGrid属性&#34;。
答案 6 :(得分:0)
我真的不这么认为,除非有一些时髦的反思可以把它拉下来。属性装饰在编译时设置,据我所知是固定的
答案 7 :(得分:0)
与此同时,我得出了一个部分解决方案,来自以下文章:
基本上你会创建一个通用类CustomTypeDescriptorWithResources<T>
,通过反射获取属性并从文件加载Description
和Category
(我想你需要显示本地化的文本,所以你可以使用资源文件(.resx
))
答案 8 :(得分:0)
这是一种“欺骗”的方式:
如果属性参数具有固定数量的常量潜在值,则可以为参数的每个潜在值定义单独的属性(并为每个属性赋予稍微不同的属性),然后切换动态引用的属性。
在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
答案 9 :(得分:-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);