1

I have a requirement that several of my colleagues' configuration files should be displayed uniformly with the PropertyGrid control, which I have implemented with reference to the following post:https://www.codeproject.com/Articles/193462/Using-PropertyGrid-to-Display-and-Edit-Dynamic-Obj.

My way is: define a ConfigObject object first, then deserialized json configuration file into ConfigObject object using JsonConvert.Convert(Newtonsoft.Json), and then assigned to the PropertyGrid.SelectedObject. But this way I can only display and edit the one level of nested json objects, if more than two levels of nested structure, so the nested property field will be not editable.

Consider the following two-level JSON structure:

{
  "DAMultiCast": "18:80:c2:00:00:0e",
  "SA": "18:60:24:A8:77:FF",
  "gPTPType": "0x88f7",
  "AVTPType": "0x22f0",
  "Initial": {
    "SyncMessageType": "0x10",
    "FollowupMessageType": "0x18",
    "ReqMessageType": "0x12",
    "RespMessageType": "0x13",
    "RespFollowupMessageType": "0x1A",
    "versionPTP": "0x02",
    "SyncMessagelength": "44",
    "FollowupMessagelength": "76",
    "ReqMessagelength": "54",
    "subdomainnumber": "0",
    "resv0": "0x00",
    "Syncflagfield": "0x0208",
    "Followupflagfield": "0x0008",
    "correctionField": "00:00:00:00:00:00:00:00",
    "resv1": "00:00:00:00",
    "SyncClockIdentity": "01:02:03:ff:fe:46:76:34",
    "RespClockIdentity": "00:22:97:ff:fe:80:0d:f2",
    "sourcePortId": "0x0001",
    "sequenceId": "143",
    "SyncControlField": "0",
    "FollowupControlField": "2",
    "DelayReqControlField": "5",
    "logMessagePeriod": "-3",
    "tlvType": "3",
    "lengthField": "28",
    "organizationId": "32962",
    "organizationSubType": "1",
    "cumulativeScaledRateOffset": "0",
    "gmTimeBaseIndicator": "0",
    "lastGmPhaseChange": "00:00:00:00:00:00:00:00:00:00:00:00",
    "scaledLastGmFreqChange": "0",
    "requestingPortIdentity": "01:02:03:ff:fe:46:76:34",
    "requestingPortId": "1"
  },
  "TM1_TG1_6.1.1B": {
    "WaitTime1": "10",
    "WaitTime2": "2"
  }
}
Jack Zhang
  • 25
  • 5
  • By the way: Nested JSON property fields appear as Collections in the PropertyGrid control, and the JToken Collection editor is displayed when clicked the [...]. – Jack Zhang Aug 12 '20 at 07:52

1 Answers1

2

This might give you a hint to achieve your goal.

Source1: Dynamically Create a Class at Runtime
Source2: PropertyGrid Browsable not found for entity framework created property, how to find it?
Source3: Make a Property Read-Only in PropertyGrid

OUTPUT:
enter image description here
CODE:

private void loadJsonToPropertyGrid(string jsonString)
    {
        var jsonObject = JsonConvert.DeserializeObject<JObject>(jsonString);
        var obj = createClass("Item", jsonObject);
        var customClass = JsonConvert.DeserializeObject(jsonString, obj.GetType());
        var customClassType = customClass.GetType();

        DynamicTypeDescriptor typeDescriptor = new DynamicTypeDescriptor(customClassType);

        var propertyDescriptorList = typeDescriptor.Properties.Cast<PropertyDescriptor>().ToList()
        .Where(p => p.PropertyType.Name != "String").ToList();

        propExpandAndReadOnly(propertyDescriptorList);

        propertyGrid1.SelectedObject = typeDescriptor.FromComponent(customClass);
    }

    private void propExpandAndReadOnly(List<PropertyDescriptor> propertyDescriptorList)
    {
        foreach (var propertyDescriptor in propertyDescriptorList)
        {
            propertyDescriptor.SetReadOnlyAttribute(true);
            propertyDescriptor.SetExpandableAttribute(true);

            DynamicTypeDescriptor typeDescriptor = new DynamicTypeDescriptor(propertyDescriptor.PropertyType);
            var chilPropertyDescriptorList = typeDescriptor.Properties.Cast<PropertyDescriptor>().ToList()
            .Where(p => p.PropertyType.Name != "String").ToList();
            propExpandAndReadOnly(chilPropertyDescriptorList);
        }
    }

    private Type[] getPropertiesType(string[] properties, JObject jsonObject)
    {
        var propertyTypes = new List<Type>();

        foreach (var property in properties)
        {
            var jToken = jsonObject.GetValue(property);
            Type propertyType;

            if (jToken.HasValues)
            {
                var obj = createClass(property, (JObject)jsonObject.GetValue(property));
                propertyType = obj.GetType();
            }
            else
            {
                propertyType = typeof(string);
            }

            propertyTypes.Add(propertyType);
        }

        return propertyTypes.ToArray();
    }

    private object createClass(string name, JObject jsonObject)
    {
        MyClassBuilder MCB = new MyClassBuilder(name);
        var properties = jsonObject.Properties().Select(p => p.Name).ToArray();
        var propertiesType = getPropertiesType(properties, jsonObject);
        var obj = MCB.CreateObject(properties, propertiesType);

        return obj;
    }

UPDATE
Creation of PropertyDescriptorExtensions

public static class PropertyDescriptorExtensions
{
    public static void SetReadOnlyAttribute(this PropertyDescriptor p, bool value)
    {
        var attributes = p.Attributes.Cast<Attribute>()
            .Where(x => !(x is ReadOnlyAttribute)).ToList();

        attributes.Add(new ReadOnlyAttribute(value));

        typeof(MemberDescriptor).GetProperty("AttributeArray",
            BindingFlags.Instance | BindingFlags.NonPublic)
            .SetValue((MemberDescriptor)p, attributes.ToArray());
    }

    public static void SetExpandableAttribute(this PropertyDescriptor p, bool value)
    {
        var attributes = p.Attributes.Cast<Attribute>()
            .Where(x => !(x is ReadOnlyAttribute)).ToList();

        if (value)
        {
            attributes.Add(new TypeConverterAttribute(typeof(ExpandableObjectConverter)));
        }

        typeof(MemberDescriptor).GetProperty("AttributeArray",
            BindingFlags.Instance | BindingFlags.NonPublic)
            .SetValue((MemberDescriptor)p, attributes.ToArray());
    }
}

Happy coding, cheers!

tontonsevilla
  • 2,649
  • 1
  • 11
  • 18
  • thanks,tontonsevilla.I will try it and get back to you later. – Jack Zhang Aug 14 '20 at 06:14
  • tontonsevilla, I achieve the PropertyDescriptor.SetReadOnlyAttribute by adding a SetReadOnlyAttribute extension method to PropertyDescriptor class, but I don't know how to implement SetExpandableAttribute method. – Jack Zhang Aug 17 '20 at 07:33
  • @JackZhang I updated my answer. You can check the solution there.Cheers! – tontonsevilla Aug 17 '20 at 08:32
  • hi,tontonsevilla,can I achieve the effect shown in the new question below? If I can't do this, I'm going to use a text editor to do it, which is more scalable and I'll use serialization to check for errors after the user changes. – Jack Zhang Sep 18 '20 at 03:15