2008-09-16 3 views
2

참고 : .Net 1.1을 사용하고 있지만 상위 버전을 사용하는 경우에는 완전히 반대하지 않습니다.PropertyGrid, DefaultValueAttribute, 동적 개체 및 열거 형

일부 PropertyGrid에서 동적으로 생성 된 개체를 표시하고 있습니다. 이러한 개체에는 숫자, 텍스트 및 열거 속성이 있습니다. 현재 목록에 굵게 표시되지 않도록 열거 형의 기본값을 설정하는 데 문제가 있습니다. 열거 자체도 동적으로 생성되며 기본값을 제외하고는 제대로 작동합니다.

먼저 오류의 원인이되는 경우 열거 형을 생성하는 방법을 보여 드리고자합니다. 첫 번째 줄은 사용자 지정 클래스를 사용하여 데이터베이스를 쿼리합니다. 이 행을 DataAdapter로 바꾸거나 DataSet을 데이터베이스 값으로 채우는 기본 방법으로 바꾸기 만하면됩니다. 내 열거 형을 만들려면 1 열의 문자열 값을 사용하고 있습니다.

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

이제 유형이 어떻게 생성되는지에 대해 설명합니다. 이는 크게 here으로 제공된 샘플 코드를 기반으로합니다. 본질적으로 pFeature를 데이터베이스 행으로 생각하십시오. 열을 반복하고 열 이름을 새 속성 이름으로 사용하고 열 값을 기본값으로 사용합니다. 그것은 적어도 목표입니다.

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

마지막으로, 우리는 우리가 나중에 PropertiesGrid를 사용하여 표시 할 수있는 기본 값에 그것의 인스턴스와 부하를 생성하는 유형을 사용합니다.

// Now we have our type. Let's create an instance from it: 
object generatedObject = Activator.CreateInstance(generatedType); 

// Loop over all the generated properties, and assign the default values 
PropertyInfo[] properties = generatedType.GetProperties(); 
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(generatedType); 

for(int i = 0; i < properties.Length; i++) 
{ 
    string field = properties[i].Name; 

    DefaultValueAttribute dva = (DefaultValueAttribute)props[field].Attributes[typeof(DefaultValueAttribute)]; 

    object o = dva.Value; 

    Type pType = properties[i].PropertyType; 

    if(pType.IsEnum) 
    { 
     o = Enum.Parse(pType, o.ToString(), true); 
    } 
    else 
    { 
     o = Convert.ChangeType(o, pType); 
    } 

    properties[i].SetValue(generatedObject, o, null); 
} 

return generatedObject; 

그러나 열거 형의 기본값을 가져 오려고하면 오류가 발생합니다. DefaultValueAttribute dva가 설정되지 않아 사용하려고하면 예외가 발생합니다.

우리는이 코드 세그먼트를 변경하면이에

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

:

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

가 DefaultValueAttribute의 DVA를 받고 아무 문제가 없습니다를; 그러나 필드는 기본값과 일치하지 않으므로 PropertiesGrid에서 굵게 표시됩니다.

생성 된 열거 형에 기본값을 설정할 때 왜 DefaultValueAttribute를 가져올 수 없는지 알아낼 수 있습니까? 당신이 아마 추측 할 수 있듯이, 나는 Reflection에 아직 익숙하지 않아, 이것은 나에게 모두 아주 새로운 것이다.

감사합니다.

업데이트 : Alabamasucks.blogspot에 대한 응답으로 ShouldSerialize를 사용하면 확실히 내 문제가 해결됩니다. 정상적인 클래스를 사용하여 메서드를 만들 수있었습니다. 그러나 생성 된 형식에 대해이 작업을 수행하는 방법이 확실하지 않습니다. 내가 알아낼 수있는 것으로부터, 나는 MethodBuilder를 사용하여 필드가 디폴트 값과 같은지 검사하기 위해 IL을 생성해야 할 것이다. 충분히 간단하게 들립니다.

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

내가 유사한 코드에서 Ildasm.exe를를 사용하여 IL 코드를 얻을 수 있었다, 그러나 나는 몇 가지 질문이있다 : 나는 IL 코드에서이 표현하고자합니다. IL 코드에서 val 변수를 사용하려면 어떻게해야합니까? 내 예제에서는 IL은 각 유형에 대해 다른로드 명령을 가지고 있기 때문에 까다로운 얻을 수 있습니다 확실히 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 

이의 값을 정수 (int)를 사용했다.현재 int, double, 문자열 및 열거 형을 사용하므로 코드는 유형에 따라 적응 적이어야합니다.

누구나 어떻게 할 수 있습니까? 아니면 잘못된 방향으로 가고 있습니까?

답변

3

나는 속성을 작동시키는 방법을 모르지만, 더 쉬운 방법이있다.

PropertyGrid는 DefaultValueAttribute를 확인하는 것 외에도 리플렉션을 사용하여 "ShouldSerializeProperty Name"이라는 메서드를 찾습니다. 여기서 [Property Name]은 해당 속성의 이름입니다. 이 메소드는, 프로퍼티이 디폴트 이외의 값으로 설정되어있는 경우는 true, 그렇지 않은 경우는 false를 돌려줍니다. 리플렉션을 사용하여 올바른 값을 반환하는 메서드를 만드는 것이 속성을 수정하는 것이 더 쉽습니다.

+0

ShouldSerialize [속성 이름]은 재미있는 단어입니다. "숨겨진 기능"위키에 있습니까? –

+0

MSDN (http://msdn.microsoft.com/en-us/library/53b8022e(VS.71,classic).aspx)에 설명되어 있습니다. 분명히, doens't는 당신이 옳은 것을 검색 할 때까지 스스로 발견하기 쉽습니다. –

2

DefaultValueAttribute를 사용하여 String 및 Type 매개 변수를 가져 와서 문자열 enum 값 (val.ToString)과 열거 형 유형을 전달해야합니다.