2008-09-16 7 views
2

Hinweis: Ich verwende .Net 1.1, obwohl ich nicht vollständig gegen die Antwort bin, die höhere Versionen verwenden.PropertyGrid, DefaultValueAttribute, dynamisches Objekt und Enumerationen

Ich zeige einige dynamisch erzeugte Objekte in einem PropertyGrid an. Diese Objekte verfügen über numerische, Text- und Enumerationseigenschaften. Derzeit habe ich Probleme, den Standardwert für die Aufzählungen festzulegen, damit sie in der Liste nicht immer fett angezeigt werden. Die Enumerationen selbst werden ebenfalls dynamisch generiert und scheinen mit Ausnahme des Standardwerts einwandfrei zu funktionieren.

Zunächst möchte ich zeigen, wie ich die Enumerationen im Falle, dass es den Fehler verursacht, erzeuge. Die erste Zeile verwendet eine benutzerdefinierte Klasse zum Abfragen der Datenbank. Ersetzen Sie diese Zeile einfach durch einen DataAdapter oder Ihre bevorzugte Methode zum Füllen eines DataSet mit Database-Werten. Ich verwende die Zeichenfolgenwerte in Spalte 1, um meine Aufzählung zu erstellen.

private Type GetNewObjectType(string field, ModuleBuilder module, DatabaseAccess da) 

//Query the database. 
System.Data.DataSet ds = da.QueryDB(query); 

EnumBuilder eb = module.DefineEnum(field, TypeAttributes.Public, typeof(int)); 

for(int i = 0; i < ds.Tables[0].Rows.Count; i++) 
{ 
    if(ds.Tables[0].Rows[i][1] != DBNull.Value) 
    { 
     string text = Convert.ToString(ds.Tables[0].Rows[i][1]); 

     eb.DefineLiteral(text, i); 
    } 
} 

return eb.CreateType(); 

Nun zu, wie der Typ erstellt wird. Dies basiert weitgehend auf dem mitgelieferten Beispielcode here. Stellen Sie sich im Wesentlichen pFeature als Datenbankzeile vor. Wir durchlaufen die Spalten und verwenden den Spaltennamen als neuen Eigenschaftsnamen und verwenden den Spaltenwert als Standardwert. Das ist zumindest das Ziel.

// create a dynamic assembly and module 
AssemblyName assemblyName = new AssemblyName(); 
assemblyName.Name = "tmpAssembly"; 
AssemblyBuilder assemblyBuilder = System.Threading.Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); 
ModuleBuilder module = assemblyBuilder.DefineDynamicModule("tmpModule"); 

// create a new type builder 
TypeBuilder typeBuilder = module.DefineType("BindableRowCellCollection", TypeAttributes.Public | TypeAttributes.Class); 

// Loop over the attributes that will be used as the properties names in out new type 
for(int i = 0; i < pFeature.Fields.FieldCount; i++) 
{ 
    string propertyName = pFeature.Fields.get_Field(i).Name; 
    object val = pFeature.get_Value(i); 

    Type type = GetNewObjectType(propertyName, module, da); 

    // Generate a private field 
    FieldBuilder field = typeBuilder.DefineField("_" + propertyName, type, FieldAttributes.Private); 

    // Generate a public property 
    PropertyBuilder property = 
     typeBuilder.DefineProperty(propertyName, 
     PropertyAttributes.None, 
     type, 
     new Type[0]); 

    //Create the custom attribute to set the description. 
    Type[] ctorParams = new Type[] { typeof(string) }; 
    ConstructorInfo classCtorInfo = 
     typeof(DescriptionAttribute).GetConstructor(ctorParams); 

    CustomAttributeBuilder myCABuilder = new CustomAttributeBuilder(
     classCtorInfo, 
     new object[] { "This is the long description of this property." }); 

    property.SetCustomAttribute(myCABuilder); 

    //Set the default value. 
    ctorParams = new Type[] { type }; 
    classCtorInfo = typeof(DefaultValueAttribute).GetConstructor(ctorParams); 

    if(type.IsEnum) 
    { 
     //val contains the text version of the enum. Parse it to the enumeration value. 
     object o = Enum.Parse(type, val.ToString(), true); 
     myCABuilder = new CustomAttributeBuilder(
      classCtorInfo, 
      new object[] { o }); 
    } 
    else 
    { 
     myCABuilder = new CustomAttributeBuilder(
      classCtorInfo, 
      new object[] { val }); 
    } 

    property.SetCustomAttribute(myCABuilder); 

    // The property set and property get methods require a special set of attributes: 
    MethodAttributes GetSetAttr = 
     MethodAttributes.Public | 
     MethodAttributes.HideBySig; 

    // Define the "get" accessor method for current private field. 
    MethodBuilder currGetPropMthdBldr = 
     typeBuilder.DefineMethod("get_value", 
     GetSetAttr, 
     type, 
     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_value", 
     GetSetAttr, 
     null, 
     new Type[] { type }); 

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

// Generate our type 
Type generatedType = typeBuilder.CreateType(); 

Schließlich verwenden wir diese Art eine Instanz davon und Last in den Standardwerten zu erstellen, damit wir sie später zeigen die PropertiesGrid verwenden.

Allerdings verursacht dies einen Fehler, wenn wir versuchen, den Standardwert für eine Aufzählung zu erhalten. Das DefaultValueAttribute-Dva wird nicht festgelegt und verursacht daher eine Ausnahme, wenn wir versuchen, es zu verwenden.

Wenn wir diesen Code-Segment ändern:

if(type.IsEnum) 
    { 
     object o = Enum.Parse(type, val.ToString(), true); 
     myCABuilder = new CustomAttributeBuilder(
      classCtorInfo, 
      new object[] { o }); 
    } 

dazu:

if(type.IsEnum) 
    { 
     myCABuilder = new CustomAttributeBuilder(
      classCtorInfo, 
      new object[] { 0 }); 
    } 

Es gibt keine Probleme, die Default dva bekommen; Das Feld wird jedoch im PropertiesGrid fett dargestellt, da es nicht mit dem Standardwert übereinstimmt.

Kann jemand herausfinden, warum ich das DefaultValueAttribute nicht erhalten kann, wenn ich den Standardwert auf meine generierte Aufzählung setze? Wie du dir wahrscheinlich denken kannst, bin ich noch neu in Reflection, also ist das alles ziemlich neu für mich.

Danke.

Update: Als Antwort auf alabamasucks.blogspot würde die Verwendung von ShouldSerialize sicherlich mein Problem lösen. Ich konnte die Methode mit einer normalen Klasse erstellen; Ich bin mir jedoch nicht sicher, wie dies für einen generierten Typ zu tun ist. Nach dem, was ich herausfinden kann, müsste ich MethodBuilder verwenden und die IL generieren, um zu überprüfen, ob das Feld dem Standardwert entspricht. Klingt einfach genug. Ich möchte diesen Code in IL darzustellen:

public bool ShouldSerializepropertyName() 
{ 
    return (field != val); 
} 

konnte ich den IL-Code erhalten ildasm.exe von ähnlichen Code verwenden, aber ich habe ein paar Fragen. Wie verwende ich die val-Variable im IL-Code? In meinem Beispiel habe ich einen int mit dem Wert von 0.

IL_0000: ldc.i4.s 0 
IL_0002: stloc.0 
IL_0003: ldloc.0 
IL_0004: ldarg.0 
IL_0005: ldfld  int32 TestNamespace.TestClass::field 
IL_000a: ceq 
IL_000c: ldc.i4.0 
IL_000d: ceq 
IL_000f: stloc.1 
IL_0010: br.s  IL_0012 
IL_0012: ldloc.1 
IL_0013: ret 

sicherlich das kann tückisch, weil IL einen anderen Ladebefehl für jeden Typen hat.Momentan verwende ich Ints, Doubles, Strings und Enumerationen, also muss der Code basierend auf dem Typ adaptiv sein.

Hat jemand eine Idee, wie man das macht? Oder gehe ich in die falsche Richtung?

Antwort

3

Ich bin nicht sicher, wie das Attribut funktioniert, aber es gibt eine andere Option, die einfacher sein kann.

Zusätzlich zur Suche nach DefaultValueAttribute verwendet das PropertyGrid auch Reflection, um nach einer Methode mit dem Namen "ShouldSerializeProperty Name" zu suchen, wobei [Property Name] der Name der fraglichen Eigenschaft ist. Diese Methode sollte einen Booleschen Wert zurückgeben, der wahr ist, wenn die Eigenschaft auf einen nicht standardmäßigen Wert gesetzt ist und andernfalls false. Es wäre wahrscheinlich einfacher für Sie, Reflektion zu verwenden, um eine Methode zu erstellen, die den korrekten Wert zurückgibt, um das Attribut zu reparieren.

+0

ShouldSerialize [Eigenschaftenname] ist ein interessanter Leckerbissen. Ist das in den "versteckten Funktionen" Wiki? –

+0

Es ist auf MSDN unter http://msdn.microsoft.com/en-us/library/53b8022e(VS.71,classic).aspx dokumentiert. Offensichtlich macht es das nicht leicht, es auf eigene Faust zu entdecken, es sei denn, Sie suchen nach dem richtigen Ding. –

2

Sie sollten es mit dem DefaultValueAttribute versuchen, indem Sie einen String- und einen Type-Parameter verwenden, indem Sie den String enum value (val.ToString) und den Typ Ihrer enum übergeben.