发一个PropertyGrid控件展示嵌套json文件结构的示例代码

lianying168000 2020-09-18 12:02:27
加精
这个帖子是之前我发的一个帖子的后续帖(https://bbs.csdn.net/topics/396829530),现在把我的实现完整代码贴出来,希望能帮到各位。
这个帖子的缘起是因为我在做一个测试工具,可集成运行多个同事写的exe测试脚本程序,然后测试程序都需要有json格式的配置文件,就想着在工具界面上展示这个配置文件并可以编辑保存。有两种方案可选:一个就是直接将配置文件以文本显示在界面上,然后保存的时候以是否能序列化成功作为校验手段,这种方案的优点就是可扩展性更高,缺点可能就是没有PropertyGrid控件来得美观吧。第二个方案就是使用PropertyGrid来展示,但这个方案对于一级的json结构比较容易实现,对于嵌套的结构以及包含数组类型的结构比较难实现(对于包含数组类型的结构,我还没实现)。
下面代码的实现思路是:用了2次反序列化,第一次为构造class做准备,第二次用来根据创建的class类型信息去填充值,然后将TypeDescriptor的Properties中PropertyType不是String的属性添加ReadOnlyAttribute和ExpandableAttribute,ReadOnlyAttribute是为了让里层的属性对象本身不可以编辑,ExpandableAttribute是让里层属性对象的子属性可以编辑。
按照下面的代码可以实现2层json嵌套结构的展示和编辑,若是多于2层需要自己根据代码去修改(可扩展性不是太强,这也就需要你能够预见到你的展示对象的结构大致不会超过几层,后面又遇到了展示数组结构的需求,还不晓得怎么去实现)。
类型构造器code:
public class ClassBuilder {
AssemblyName assemblyName;
public ClassBuilder(string className) {
this.assemblyName = new AssemblyName(className);
}
public object CreateObject(string[] propertyNames, Type[] types) {
if (propertyNames.Length != types.Length) {
System.Windows.Forms.MessageBox.Show("属性名数量和类型数量不匹配.");
}

TypeBuilder DynamicClass = this.CreateClass();
this.CreateConstructor(DynamicClass);
for (int i = 0; i < propertyNames.Length; i++) {
CreateProperty(DynamicClass, propertyNames[i], types[i]);
}
Type type = DynamicClass.CreateType();

return Activator.CreateInstance(type);
}

private void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType) {
FieldBuilder fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
MethodBuilder getPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
ILGenerator getIl = getPropMthdBldr.GetILGenerator();

getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, fieldBuilder);
getIl.Emit(OpCodes.Ret);

MethodBuilder setPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new[] { propertyType });

ILGenerator setIl = setPropMthdBldr.GetILGenerator();
Label modifyProperty = setIl.DefineLabel();
Label exitSet = setIl.DefineLabel();

setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);

setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);

propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setPropMthdBldr);
}

private void CreateConstructor(TypeBuilder typeBuilder) {
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
}

private TypeBuilder CreateClass() {
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(this.assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType(this.assemblyName.FullName, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout, null);
return typeBuilder;
}
}

被限制回复了,需要帮顶下帖子。。。
...全文
39371 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
hookee 2021-03-03
  • 打赏
  • 举报
回复
Giberson1 2021-01-13
  • 打赏
  • 举报
回复
多层用嵌套就好了,让你不好好学数据结构。
Drwang999 2020-10-21
  • 打赏
  • 举报
回复
哇塞,这么多高技术含量的内容啊,顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶
dao61wang 2020-10-19
  • 打赏
  • 举报
回复
lianying168000 2020-10-14
  • 打赏
  • 举报
回复
确实用PropertyGrid控件去实现多层嵌套和包含数组结构的话有点吃力。
lianying168000 2020-10-14
  • 打赏
  • 举报
回复
引用 8 楼 ziqi0716 的回复:
[quote=引用 7 楼 ziqi0716 的回复:]vs code的配置文件是Json格式的,而且可以可视化编辑,实现逻辑可以去github上看下实现.
typescript实现的,逻辑参考下.或者直接挖过来嵌入浏览器插件.[/quote] 你意思是用typescript或vs code的思路去实现,然后嵌入到winform里的webrowser控件中?
lianying168000 2020-10-14
  • 打赏
  • 举报
回复
引用 13 楼 贵阳老马马善福专业维修游泳池堵漏防水工程 的回复:
感谢分享,但是建议你打包放在下载频道。
好的 等忙完手头的事就上传。
threenewbee 2020-10-14
  • 打赏
  • 举报
回复
感谢分享,但是建议你打包放在下载频道。
lianying168000 2020-10-14
  • 打赏
  • 举报
回复
引用 11 楼 ziqi0716 的回复:
[quote=引用 8 楼 ziqi0716 的回复:] 用这个思路实现的话直接就用C#了呀,不用内嵌浏览器,内嵌浏览器的意思是直接把vscode的实现代码拿过来用.
嗯 有空了去研究下 多谢
ziqi0716 2020-10-14
  • 打赏
  • 举报
回复
引用 9 楼 lianying168000 的回复:
[quote=引用 8 楼 ziqi0716 的回复:][quote=引用 7 楼 ziqi0716 的回复:]vs code的配置文件是Json格式的,而且可以可视化编辑,实现逻辑可以去github上看下实现.
typescript实现的,逻辑参考下.或者直接挖过来嵌入浏览器插件.[/quote] 你意思是用typescript或vs code的思路去实现,然后嵌入到winform里的webrowser控件中?[/quote] 用这个思路实现的话直接就用C#了呀,不用内嵌浏览器,内嵌浏览器的意思是直接把vscode的实现代码拿过来用.
ziqi0716 2020-10-10
  • 打赏
  • 举报
回复
引用 7 楼 ziqi0716 的回复:
vs code的配置文件是Json格式的,而且可以可视化编辑,实现逻辑可以去github上看下实现.
typescript实现的,逻辑参考下.或者直接挖过来嵌入浏览器插件.
ziqi0716 2020-10-10
  • 打赏
  • 举报
回复
vs code的配置文件是Json格式的,而且可以可视化编辑,实现逻辑可以去github上看下实现.
lianying168000 2020-09-18
  • 打赏
  • 举报
回复
DynamicTypeDescriptor Part 3:
public override string ToString() {
            return base.ToString() + " (" + Component + ")";
        }

        public DynamicTypeDescriptor FromComponent(object component) {
            if (component == null) {
                throw new ArgumentNullException("component");
            }
            if (!_type.IsAssignableFrom(component.GetType())) {
                throw new ArgumentException(null, "component");
            }
            DynamicTypeDescriptor desc = new DynamicTypeDescriptor();
            desc._type = _type;
            desc.Component = component;

            desc._typeConverter = _typeConverter;
            desc._editors = _editors;
            desc._defaultEvent = _defaultEvent;
            desc._defaultProperty = _defaultProperty;
            desc._attributes = _attributes;
            desc._events = _events;
            desc.OriginalProperties = OriginalProperties;

            List<PropertyDescriptor> properties = new List<PropertyDescriptor>();
            foreach (PropertyDescriptor pd in Properties) {
                DynamicProperty dp = new DynamicProperty(desc, pd, component);
                properties.Add(dp);
            }

            desc.Properties = new PropertyDescriptorCollection(properties.ToArray());
            return desc;
        }

        public object Component { get; private set; }
        public string ClassName { get; set; }
        public string ComponentName { get; set; }

        public AttributeCollection GetAttributes() {
            return _attributes;
        }

        public string GetClassName() {
            if (ClassName != null) {
                return ClassName;
            }
            if (Component != null) {
                return Component.GetType().Name;
            }
            if (_type != null) {
                return _type.Name;
            }
            return null;
        }

        public string GetComponentName() {
            if (ComponentName != null) {
                return ComponentName;
            }
            return Component != null ? Component.ToString() : null;
        }

        public TypeConverter GetConverter() {
            return _typeConverter;
        }

        public EventDescriptor GetDefaultEvent() {
            return _defaultEvent;
        }

        public PropertyDescriptor GetDefaultProperty() {
            return _defaultProperty;
        }



        public EventDescriptorCollection GetEvents() {
            return _events;
        }

        public EventDescriptorCollection GetEvents(Attribute[] attributes) {
            return _events;
        }

        public PropertyDescriptorCollection GetProperties() {
            return Properties;
        }

        public PropertyDescriptorCollection GetProperties(Attribute[] attributes) {
            return Properties;
        }

        public object GetPropertyOwner(PropertyDescriptor pd) {
            return Component;
        }

        public object GetEditor(Type editorBaseType) {
            object editor;
            if (_editors.TryGetValue(editorBaseType, out editor)) {
                return editor;
            }
            return null;
        }
    }
lianying168000 2020-09-18
  • 打赏
  • 举报
回复
DynamicTypeDescriptor Part 2:
public override Type PropertyType {
                get {
                    if (_existing != null) {
                        return _existing.PropertyType;
                    }

                    return _type;
                }
            }

            public override void ResetValue(object component) {
                if (_existing != null) {
                    _existing.ResetValue(component);
                    PropertyChangedEventHandler handler = PropertyChanged;
                    if (handler != null) {
                        handler(this, new PropertyChangedEventArgs(Name));
                    }
                    _descriptor.OnValueChanged(this);
                    return;
                }

                if (CanResetValue(component)) {
                    Value = _defaultValue;
                    _descriptor.OnValueChanged(this);
                }
            }

            public override void SetValue(object component, object value) {
                if (_existing != null) {
                    _existing.SetValue(component, value);
                    PropertyChangedEventHandler handler = PropertyChanged;
                    if (handler != null) {
                        handler(this, new PropertyChangedEventArgs(Name));
                    }
                    _descriptor.OnValueChanged(this);
                    return;
                }
                Value = value;
                _descriptor.OnValueChanged(this);
            }

            public override bool ShouldSerializeValue(object component) {
                if (_existing != null) {
                    return _existing.ShouldSerializeValue(component);
                }
                return false;
            }

            public override object GetEditor(Type editorBaseType) {
                if (editorBaseType == null) {
                    throw new ArgumentNullException("editorBaseType");
                }

                if (_editors != null) {
                    object type;
                    if ((_editors.TryGetValue(editorBaseType, out type)) && (type != null)) {
                        return type;
                    }
                }
                return base.GetEditor(editorBaseType);
            }

            public void SetEditor(Type editorBaseType, object obj) {
                if (editorBaseType == null) {
                    throw new ArgumentNullException("editorBaseType");
                }

                if (_editors == null) {
                    if (obj == null) {
                        return;
                    }

                    _editors = new Dictionary<Type, object>();
                }
                if (obj == null) {
                    _editors.Remove(editorBaseType);
                } else {
                    _editors[editorBaseType] = obj;
                }
            }
        }

        public PropertyDescriptor AddProperty(Type type, string name, object value, string displayName, string description, string category, bool hasDefaultValue, object defaultValue, bool readOnly) {
            return AddProperty(type, name, value, displayName, description, category, hasDefaultValue, defaultValue, readOnly, null);
        }

        public PropertyDescriptor AddProperty(Type type, string name, object value, string displayName, string description, string category, bool hasDefaultValue, object defaultValue, bool readOnly, Type uiTypeEditor) {
            if (type == null) {
                throw new ArgumentNullException("type");
            }
            if (name == null) {
                throw new ArgumentNullException("name");
            }
            List<Attribute> atts = new List<Attribute>();
            if (!string.IsNullOrEmpty(displayName)) {
                atts.Add(new DisplayNameAttribute(displayName));
            }
            if (!string.IsNullOrEmpty(description)) {
                atts.Add(new DescriptionAttribute(description));
            }
            if (!string.IsNullOrEmpty(category)) {
                atts.Add(new CategoryAttribute(category));
            }
            if (hasDefaultValue) {
                atts.Add(new DefaultValueAttribute(defaultValue));
            }
            if (uiTypeEditor != null) {
                atts.Add(new EditorAttribute(uiTypeEditor, typeof(UITypeEditor)));
            }
            if (readOnly) {
                atts.Add(new ReadOnlyAttribute(true));
            }

            DynamicProperty property = new DynamicProperty(this, type, value, name, atts.ToArray());
            AddProperty(property);
            return property;
        }

        public void AddProperty(PropertyDescriptor property) {
            if (property == null) {
                throw new ArgumentNullException("property");
            }
            Properties.Add(property);
        }

        public void RemoveProperty(string name) {
            if (name == null) {
                throw new ArgumentNullException("name");
            }

            List<PropertyDescriptor> remove = new List<PropertyDescriptor>();
            foreach (PropertyDescriptor pd in Properties) {
                if (pd.Name == name) {
                    remove.Add(pd);
                }
            }

            foreach (PropertyDescriptor pd in remove) {
                Properties.Remove(pd);
            }
        }
lianying168000 2020-09-18
  • 打赏
  • 举报
回复
DynamicTypeDescriptor Part 1:
    
public sealed class DynamicTypeDescriptor : ICustomTypeDescriptor, INotifyPropertyChanged {
        private Type _type;
        private AttributeCollection _attributes;
        private TypeConverter _typeConverter;
        private Dictionary<Type, object> _editors;
        private EventDescriptor _defaultEvent;
        private PropertyDescriptor _defaultProperty;
        private EventDescriptorCollection _events;

        public PropertyDescriptorCollection OriginalProperties { get; private set; }
        public PropertyDescriptorCollection Properties { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        private DynamicTypeDescriptor() {
        }

        public DynamicTypeDescriptor(Type type) {
            if (type == null) {
                throw new ArgumentNullException("type");
            }

            _type = type;
            _typeConverter = TypeDescriptor.GetConverter(type);
            _defaultEvent = TypeDescriptor.GetDefaultEvent(type);
            _defaultProperty = TypeDescriptor.GetDefaultProperty(type);
            _events = TypeDescriptor.GetEvents(type);

            List<PropertyDescriptor> normalProperties = new List<PropertyDescriptor>();
            OriginalProperties = TypeDescriptor.GetProperties(type);
            foreach (PropertyDescriptor property in OriginalProperties) {
                if (!property.IsBrowsable) {
                    continue;
                }
                normalProperties.Add(property);
            }
            Properties = new PropertyDescriptorCollection(normalProperties.ToArray());

            _attributes = TypeDescriptor.GetAttributes(type);

            _editors = new Dictionary<Type, object>();
            object editor = TypeDescriptor.GetEditor(type, typeof(UITypeEditor));
            if (editor != null) {
                _editors.Add(typeof(UITypeEditor), editor);
            }
            editor = TypeDescriptor.GetEditor(type, typeof(ComponentEditor));
            if (editor != null) {
                _editors.Add(typeof(ComponentEditor), editor);
            }
            editor = TypeDescriptor.GetEditor(type, typeof(InstanceCreationEditor));
            if (editor != null) {
                _editors.Add(typeof(InstanceCreationEditor), editor);
            }
        }

        public T GetPropertyValue<T>(string name, T defaultValue) {
            if (name == null) {
                throw new ArgumentNullException("name");
            }

            foreach (PropertyDescriptor pd in Properties) {
                if (pd.Name == name) {
                    try {
                        return (T)Convert.ChangeType(pd.GetValue(Component), typeof(T));
                    } catch {
                        return defaultValue;
                    }
                }
            }
            return defaultValue;
        }

        public void SetPropertyValue(string name, object value) {
            if (name == null) {
                throw new ArgumentNullException("name");
            }
            foreach (PropertyDescriptor pd in Properties) {
                if (pd.Name == name) {
                    pd.SetValue(Component, value);
                    break;
                }
            }
        }

        internal void OnValueChanged(PropertyDescriptor prop) {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) {
                handler(this, new PropertyChangedEventArgs(prop.Name));
            }
        }

        internal static T GetAttribute<T>(AttributeCollection attributes) where T : Attribute {
            if (attributes == null) {
                return null;
            }
            foreach (Attribute attribute in attributes) {
                if (typeof(T).IsAssignableFrom(attribute.GetType())) {
                    return (T)attribute;
                }
            }
            return null;
        }

        public sealed class DynamicProperty : PropertyDescriptor, INotifyPropertyChanged {
            private readonly Type _type;
            private readonly bool _hasDefaultValue;
            private readonly object _defaultValue;
            private readonly PropertyDescriptor _existing;
            private readonly DynamicTypeDescriptor _descriptor;
            private Dictionary<Type, object> _editors;
            private bool? _readOnly;
            private bool? _browsable;
            private string _displayName;
            private string _description;
            private string _category;
            private List<Attribute> _attributes = new List<Attribute>();

            public event PropertyChangedEventHandler PropertyChanged;

            internal DynamicProperty(DynamicTypeDescriptor descriptor, Type type, object value, string name, Attribute[] attrs) : base(name, attrs) {
                _descriptor = descriptor;
                _type = type;
                Value = value;
                DefaultValueAttribute def = DynamicTypeDescriptor.GetAttribute<DefaultValueAttribute>(Attributes);
                if (def == null) {
                    _hasDefaultValue = false;
                } else {
                    _hasDefaultValue = true;
                    _defaultValue = def.Value;
                }

                if (attrs != null) {
                    foreach (Attribute att in attrs) {
                        _attributes.Add(att);
                    }
                }
            }

            internal static Attribute[] GetAttributes(PropertyDescriptor existing) {
                List<Attribute> atts = new List<Attribute>();
                foreach (Attribute a in existing.Attributes) {
                    atts.Add(a);
                }
                return atts.ToArray();
            }

            internal DynamicProperty(DynamicTypeDescriptor descriptor, PropertyDescriptor existing, object component) : this(descriptor, existing.PropertyType, existing.GetValue(component), existing.Name, GetAttributes(existing)) {
                _existing = existing;
            }

            public void RemoveAttributesOfType<T>() where T : Attribute {
                List<Attribute> remove = new List<Attribute>();
                foreach (Attribute attribute in _attributes) {
                    if (typeof(T).IsAssignableFrom(attribute.GetType())) {
                        remove.Add(attribute);
                    }
                }

                foreach (Attribute attribute in remove) {
                    _attributes.Remove(attribute);
                }
            }

            public IList<Attribute> AttributesList {
                get {
                    return _attributes;
                }
            }

            public override AttributeCollection Attributes {
                get {
                    return new AttributeCollection(_attributes.ToArray());
                }
            }

            public object Value { get; set; }

            public override bool CanResetValue(object component) {
                if (_existing != null) {
                    return _existing.CanResetValue(component);
                }
                return _hasDefaultValue;
            }

            public override Type ComponentType {
                get {
                    if (_existing != null) {
                        return _existing.ComponentType;
                    }
                    return typeof(object);
                }
            }

            public override object GetValue(object component) {
                if (_existing != null) {
                    return _existing.GetValue(component);
                }
                return Value;
            }

            public override string Category {
                get {
                    if (_category != null) {
                        return _category;
                    }
                    return base.Category;
                }
            }

            public void SetCategory(string category) {
                _category = category;
            }

            public override string Description {
                get {
                    if (_description != null) {
                        return _description;
                    }
                    return base.Description;
                }
            }

            public void SetDescription(string description) {
                _description = description;
            }

            public override string DisplayName {
                get {
                    if (_displayName != null) {
                        return _displayName;
                    }
                    if (_existing != null) {
                        return _existing.DisplayName;
                    }
                    return base.DisplayName;
                }
            }

            public void SetDisplayName(string displayName) {
                _displayName = displayName;
            }

            public override bool IsBrowsable {
                get {
                    if (_browsable.HasValue) {
                        return _browsable.Value;
                    }
                    return base.IsBrowsable;
                }
            }

            public void SetBrowsable(bool browsable) {
                _browsable = browsable;
            }

            public override bool IsReadOnly {
                get {
                    if (_readOnly.HasValue) {
                        return _readOnly.Value;
                    }
                    if (_existing != null) {
                        return _existing.IsReadOnly;
                    }

                    ReadOnlyAttribute att = DynamicTypeDescriptor.GetAttribute<ReadOnlyAttribute>(Attributes);
                    if (att == null) {
                        return false;
                    }
                    return att.IsReadOnly;
                }
            }
lianying168000 2020-09-18
  • 打赏
  • 举报
回复
显示效果如下图,Ethernet和MatrixFilePath都是嵌套的二级json对象,红圈框住的是数组结构,还没实现怎么展示和编辑数组结构。
lianying168000 2020-09-18
  • 打赏
  • 举报
回复
PropertyDescriptorExtensions code:
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());
        }
    }
然后调用的code如下:
try {
                string configInfo = string.Empty;
                ReadConfigInfo(out configInfo);
                var configObject = JsonConvert.DeserializeObject<JObject>(configInfo);
                var obj = CreateClass("config", configObject);
                var customClass = JsonConvert.DeserializeObject(configInfo, 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);
                m_config = typeDescriptor.FromComponent(customClass);
                propertyGrid1.SelectedObject = m_config;
            } catch (Exception ex) {
                MessageBox.Show(ex.ToString());
            }
wanghui0380 2020-09-18
  • 打赏
  • 举报
回复
不错的帖子,不过太长了,大多数人不会看了。先留个爪,后面在聊 我提一个东西把,既然用了json,我们说未来可能需要研究一下JSchema schema 当然Protocolbuf也行。 要说编辑json,我个人不会这么麻烦,就editor直接编了,校验一下是否合乎标准就好 至于上面为啥说JSchema schema,没结构,没校验。枚举,可选列表都没办法。还是要带结构,所以用个公用的地址存一下结构就好。 用JSchema schema直接生成UI其实也可以的 比如json第一句 { "$schema": "http://xxxx/schemas/xxxx.json#\ } 这样就可以了 其实目前大厂基本如此,因为参与的人多,小组多。协议版本和更新都采用这种方式,在公用地址放JSchema schema和protocol idl,这样其他小组的人才能进行自动化的测试和自动化的异种语言同步代码

8,834

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 组件/控件开发
社区管理员
  • 组件/控件开发社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧