2008-09-09 16 views
57

Ich bin mir nicht sicher, ob es möglich ist, Attributparameter während der Laufzeit zu ändern? Zum Beispiel innerhalb einer Baugruppe habe ich die folgende KlasseParameter des Attributs zur Laufzeit ändern

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

Dies ist eine Klasse, die von einem Drittanbieter zur Verfügung gestellt wird und Ich kann den Code ändern. Aber jetzt habe ich festgestellt, dass die obigen Beschreibungen nicht korrekt sind, und ich möchte den Kategorienamen "change me" in etwas anderes ändern, wenn ich eine Instanz der obigen Klasse an ein Eigenschaftsraster binde.

Darf ich wissen, wie das geht?

Antwort

0

Ich glaube wirklich nicht, es sei denn, es gibt einige funky Reflexion, die es abziehen können. Die Eigenschaft Dekorationen sind bei der Kompilierung festgelegt und meines Wissens sind

festen
24

Nun, Sie jeden Tag etwas Neues lernen, offenbar log ich:

Was ist im Allgemeinen nicht realisiert ist, dass Sie Attribut ändern können Instanz Werte ziemlich einfach zur Laufzeit. Der Grund ist, von natürlich, dass die Instanzen der Attributklassen, die erstellt werden, perfekt normale Objekte sind und ohne Einschränkung verwendet werden können. Zum Beispiel können wir das Objekt erhalten:

ASCII[] attrs1=(ASCII[]) 
    typeof(MyClass).GetCustomAttributes(typeof(ASCII), false); 

... den Wert seiner öffentlichen Variablen ändern und zeigen, dass sie sich geändert hat:

attrs1[0].MyData="A New String"; 
MessageBox.Show(attrs1[0].MyData); 

... und erstellen schließlich eine andere Instanz und zeigen, dass ihre Wert ist unverändert:

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

Nun, nicht wirklich. Sie können eine Instanz des Attributobjekts erstellen und ändern, aber es wirkt sich nicht auf die Attribute aus, die in der Eigenschaft markiert sind (da sie ihre eigene unveränderte Instanz erhalten). – denver

1

Haben Sie das Problem gelöst?

Hier sind mögliche Schritte, um eine akzeptable Lösung zu erreichen.

  1. Versuchen, ein Kind Klasse zu erstellen, der alle Eigenschaften neu definieren, dass Sie die [Category] Attribut ändern müssen (markieren sie mit new). Beispiel:
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); 

... 

} 

Später Edit:Dieser Teil der Lösung nicht praktikabel ist, wenn Sie eine große Anzahl von Eigenschaften haben, die Sie brauchen, um die Attribute neu zu schreiben. Dies ist, wo Teil zwei an seinen Platz kommt:

  1. Natürlich wird dies nicht helfen, wenn die Klasse nicht vererbbar ist, oder wenn Sie eine Menge von Objekten (und Objekten) . Sie müssten eine vollständige automatische Proxy-Klasse erstellen, die Ihre Klasse abruft und eine dynamische Klasse erstellt, Attribute anwendet und natürlich eine Verbindung zwischen den beiden Klassen herstellt.Dies ist ein wenig komplizierter, aber auch erreichbar. Benutze nur Reflexionen und du bist auf dem richtigen Weg.
+0

Bogdan, Ich fürchte, dass die Unterklasse der Klasse und machen die ganze Re-Liability ist unpraktisch, um es gelinde auszudrücken. – Graviton

+0

Wenn Sie Unterklassen erstellen, müssen Sie alle Attribute automatisch neu erstellen und die alten Attribute ersetzen. Dies ist die einfachste Lösung, wenn Sie Unterklassen erstellen können. Die Grundidee war, einen Proxy automatisch zu erstellen (mit einem dynamischen Typ) und die Attribute im laufenden Betrieb zu ersetzen. –

0

In der Zwischenzeit ich zu einer Teillösung gekommen sind, aus den folgenden Artikeln abgeleitet:

  1. ICustomTypeDescriptor, Part 1
  2. ICustomTypeDescriptor, Part 2
  3. Add (Remove) Items to (from) PropertyGrid at Runtime

Grundsätzlich würden Sie erstellen eine generische Klasse CustomTypeDescriptorWithResources<T>, die die Eigenschaften durch reflect bekommen würde Ion und Last Description und Category aus einer Datei (ich nehme an, Sie lokalisierten Text angezeigt werden müssen, so dass Sie eine Ressourcen-Datei (.resx) verwenden könnte)

4

Sie können die meisten gängigen Attribute ganz einfach diese Erweiterbarkeit bieten Unterklasse:

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

Es gibt komplexere Optionen, die benutzerdefinierte PropertyDescriptor s, ausgesetzt über TypeConverter, ICustomTypeDescriptor oder TypeDescriptionProvider beinhalten das Schreiben - aber das ist in der Regel viel des Guten.

+0

Aber Marc, er sagte, er habe keinen Zugriff auf den Code – toddmo

7

Falls jemand anderes diesen Weg geht, ist die Antwort, Sie können es tun, mit Reflexion, außer Sie können nicht, weil es einen Fehler im Framework gibt. Hier ist, wie Sie es tun würde:

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

Alles schön und gut, mit der Ausnahme, dass die Kategorie-Attribut für alle Eigenschaften verändert, nicht nur ‚Age‘.

+4

Ich würde es kaum einen Fehler nennen, wenn Sie in "BindingFlags.NonPublic" -Felder herumgehen. –

+1

Ich glaube, das ist in der Tat ein Fehler, weil es auch mit öffentlichen Eigenschaften passiert, auch wenn Sie keine "BindingFlags.NonPublic" -Felder verwenden. Weiß jemand, ob dies ausgelöst wurde? Ein Link zur Bug-Seite wäre nützlich. Die Verwendung von TypeDescriptor anstelle von Reflection hat perfekt funktioniert! – kkara

1

Da der ausgewählte Element des Property ist „Alter“:

SetCategoryLabelViaReflection(MyPropertyGrid.SelectedGridItem.Parent, 
    MyPropertyGrid.SelectedGridItem.Parent.Label, "New Category Label"); 

Wo SetCategoryLabelViaReflection() wie folgt definiert ist:

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

Soweit programmatisch das ausgewählte Element Einstellung, die übergeordnete Kategorie, von denen du möchtest dich ändern; Es gibt eine Reihe einfacher Lösungen. Google "Setzen Sie den Fokus auf eine bestimmte PropertyGrid-Eigenschaft".

-1

Sie können Attributwerte zur Laufzeit auf Klassenstufe ändern (nicht Objekt):

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

Leider Attribute nicht bestimmt sind, zur Laufzeit ändern. Sie haben grundsätzlich zwei Möglichkeiten:

  1. Erstellen Sie eine ähnliche Art on the fly mit System.Reflection.Emit wie unten gezeigt.

  2. Bitten Sie Ihren Händler, diese Funktionalität hinzuzufügen. Wenn Sie Xceed.WpfToolkit.Extended verwenden, können Sie den Quellcode von here herunterladen und problemlos eine Schnittstelle wie IResolveCategoryName implementieren, die das Attribut zur Laufzeit auflösen würde. Ich habe ein bisschen mehr als das, es war ziemlich einfach, mehr Funktionalität wie Grenzen hinzuzufügen, wenn man einen numerischen Wert in einem DoubleUpDown innerhalb des PropertyGrid, etc. bearbeitet.

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

Für die erste Option: Dies ist jedoch Mangel an geeigneter Eigenschaft das Ergebnis zurück auf das eigentliche Objekt zu reflektieren Bindung bearbeitet wird.

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

Hier ist ein „cheaty“ Art und Weise, es zu tun:

Wenn Sie eine feste Anzahl von konstanten Potential Werte für das Attribut Parameter haben, können Sie eine separate Eigenschaft für jeden potentiellen Wert des Parameters definieren (und geben Sie jeder Eigenschaft das etwas andere Attribut an), dann wechseln Sie, welche Eigenschaft Sie dynamisch referenzieren.

In VB.NET, könnte es so aussehen:

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