Я не уверен, можно ли изменить параметр атрибута во время выполнения? Например, внутри сборки у меня есть следующий класс
public class UserInfo
{
[Category("change me!")]
public int Age
{
get;
set;
}
[Category("change me!")]
public string Name
{
get;
set;
}
}
Это класс, предоставляемый сторонним поставщиком и Я не могу изменить код. Но теперь я обнаружил, что приведенные выше описания неточны, и я хочу изменить название категории «смени меня» на другое, когда я привязываю экземпляр указанного выше класса к сетке свойств.
Могу я узнать, как это сделать?





Я действительно так не думаю, если только нет какого-нибудь фанкового отражения, которое может это сделать. Декорации свойств устанавливаются во время компиляции и, насколько мне известно, исправлены
Ну ты каждый день узнаешь что-то новое, видимо соврал:
What isn’t generally realised is that you can change attribute instance values fairly easily at runtime. The reason is, of course, that the instances of the attribute classes that are created are perfectly normal objects and can be used without restriction. For example, we can get the object:
ASCII[] attrs1=(ASCII[]) typeof(MyClass).GetCustomAttributes(typeof(ASCII), false);…change the value of its public variable and show that it has changed:
attrs1[0].MyData = "A New String"; MessageBox.Show(attrs1[0].MyData);…and finally create another instance and show that its value is unchanged:
ASCII[] attrs3=(ASCII[]) typeof(MyClass).GetCustomAttributes(typeof(ASCII), false); MessageBox.Show(attrs3[0].MyData);
http://www.vsj.co.uk/articles/display.asp?id=713
Вы решили проблему?
Вот возможные шаги для достижения приемлемого решения.
[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); ... }
Позже редактировать:Эта часть решения не работает, если у вас есть большое количество свойств, которые могут потребоваться для перезаписи атрибутов. Здесь начинается вторая часть:
Богдан, я боюсь, что создавать подклассы класса и делать все уточнения непрактично, мягко говоря.
Если вы создаете подклассы, вам придется автоматически воссоздать все атрибуты и заменить старые атрибуты. Это самое простое решение, если вы можете создать подкласс. Основная идея заключалась в автоматическом создании прокси (с использованием динамического типа) и замене атрибутов на лету.
Тем временем я пришел к частичному решению, основанному на следующих статьях:
В основном вы должны создать общий класс CustomTypeDescriptorWithResources<T>, который будет получать свойства через отражение и загружать Description и Category из файла (я полагаю, вам нужно отобразить локализованный текст, чтобы вы могли использовать файл ресурсов (.resx))
Вы можете легко подклассифицировать большинство общих атрибутов, чтобы обеспечить эту расширяемость:
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, но это обычно излишне.
Но Марк, он сказал, что у него нет доступа к коду
В случае, если кто-то еще пойдет по этой аллее, ответ - вы можете сделать это с отражением, но вы не можете, потому что в структуре есть ошибка. Вот как бы вы это сделали:
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")
Все хорошо, за исключением того, что атрибут категории изменен для всех свойств, а не только для «Возраст».
Я бы вряд ли назвал это ошибкой, если вы будете вмешиваться в поля "BindingFlags.NonPublic".
Я считаю, что это действительно ошибка, потому что это также происходит с общедоступными свойствами, даже если вы не используете поля BindingFlags.NonPublic. Кто-нибудь знает, поднимался ли этот вопрос? Ссылка на страницу с ошибкой была бы полезна. Использование TypeDescriptor вместо Reflection сработало отлично!
Учитывая, что выбранным элементом 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».
Вы можете изменить значения атрибутов во время выполнения на уровне класса (не объекта):
var attr = TypeDescriptor.GetProperties(typeof(UserContact))["UserName"].Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute;
attr.GetType().GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(attr, username_readonly);
К сожалению, атрибуты не должны изменяться во время выполнения. В основном у вас есть два варианта:
Создайте аналогичный тип на лету с помощью System.Reflection.Emit, как показано ниже.
Попросите вашего поставщика добавить эту функцию. Если вы используете 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;
}
}
Вот "хитрый" способ сделать это:
Если у вас есть фиксированное количество постоянных потенциальных значений для параметра атрибута, вы можете определить отдельное свойство для каждого потенциального значения параметра (и присвоить каждому свойству немного другой атрибут), а затем переключить, какое свойство вы ссылаетесь динамически.
В 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
Ну не совсем. Вы можете создать экземпляр объекта атрибута и изменить его, но это не повлияет ни на что, используя атрибуты, отмеченные в свойстве (поскольку они получат свой собственный неизмененный экземпляр).