2008-09-09 44 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