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?
ShouldSerialize [Eigenschaftenname] ist ein interessanter Leckerbissen. Ist das in den "versteckten Funktionen" Wiki? –
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. –