【问题标题】:How to display a dynamic object in property grid?如何在属性网格中显示动态对象?
【发布时间】:2011-03-30 07:49:03
【问题描述】:

我有一个自定义对象类型,它必须在 PropertyGrid 中可编辑:

public class CustomObjectType
{
    public string Name { get; set; }        
    public List<CustomProperty> Properties {get; set;}
}

其中有一个自定义属性列表:

public class CustomProperty
{
    public string Name { get; set; }
    public string Desc { get; set; }
    public Object DefaultValue { get; set; }    
    Type type;

    public Type Type
    {
        get
        {
            return type;
        }
        set
        {
                type = value;
                DefaultValue = Activator.CreateInstance(value);
        }              
    }
}

这里的主要问题是PropertyGrid 控件不允许编辑,也没有为属性DefaultValue 使用适当的编辑工具,该属性是通过设置CustomProperty 的字段Type 预先实例化的。

DefaultValue 的类型仅在运行时才知道。

此外,我需要为CustomProperty 的属性Type 提供自定义TypeConverter,以显示支持类型的下拉列表(例如,IntStringColor、@ 987654335@).

我该怎么做?

【问题讨论】:

    标签: c# winforms propertygrid


    【解决方案1】:
    public override void SetValue(object component, object value)           
    {
        //((CustomObjectType)component)[prop.Name] = value;
    
        CustomObjectType cot = (CustomObjectType)component;
    
        CustomProperty cp = cot.Properties.FirstOrDefault(r => r.Name.Equals(prop.Name));
        if (cp == null) return;
    
        cp.DefaultValue = value;
    }
    

    【讨论】:

      【解决方案2】:

      我认为 Marc Gravell 可能有点误解了上下文。

      我试图编辑 CustomObjectTypes 的属性而不是“CustomObjects”本身。

      这是修改后的 Marc 的代码:

      using System;
      using System.Collections.Generic;
      using System.ComponentModel;
      using System.Windows.Forms;
      
      public class CustomObjectType
      {
          [Category("Standard")]
          public string Name { get; set; }
          [Category("Standard")]
          public List<CustomProperty> Properties {get;set;}
      
          public CustomObjectType()
          {
              Properties = new List<CustomProperty>();
          }
      }
      
      [TypeConverter(typeof(ExpandableObjectConverter))]
      public class Person
      {
          public string Name {get;set;}
          public DateTime DateOfBirth { get; set; }
          public int Age { get; set; }
      }
      
      [TypeConverter(typeof(CustomProperty.CustomPropertyConverter))]
      public class CustomProperty
      {
          public CustomProperty()
          {
              Type = typeof(int);
              Name = "SomeProperty";    
          }
      
          private class CustomPropertyConverter : ExpandableObjectConverter
          {
              public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
              {
                  var stdProps = base.GetProperties(context, value, attributes);
                  CustomProperty obj = value as CustomProperty;            
                  PropertyDescriptor[] props = new PropertyDescriptor[stdProps.Count + 1];
                  stdProps.CopyTo(props, 0);
                  props[stdProps.Count] = new ObjectDescriptor(obj);
      
                  return new PropertyDescriptorCollection(props);
              }
          }
          private class ObjectDescriptor : PropertyDescriptor
          {
              private readonly CustomProperty prop;
              public ObjectDescriptor(CustomProperty prop)
                  : base(prop.Name, null)
              {
                  this.prop = prop;
              }
              public override string Category { get { return "Standard"; } }
              public override string Description { get { return "DefaultValue"; } }
              public override string Name { get { return "DefaultValue"; } }
              public override string DisplayName { get { return "DefaultValue"; } }
              public override bool ShouldSerializeValue(object component) { return ((CustomProperty)component).DefaultValue != null; }
              public override void ResetValue(object component) { ((CustomProperty)component).DefaultValue = null; }
              public override bool IsReadOnly { get { return false; } }
              public override Type PropertyType { get { return prop.Type; } }
              public override bool CanResetValue(object component) { return true; }
              public override Type ComponentType { get { return typeof(CustomProperty); } }
              public override void SetValue(object component, object value) { ((CustomProperty)component).DefaultValue = value; }
              public override object GetValue(object component) { return ((CustomProperty)component).DefaultValue; }
          }
      
          private class CustomTypeConverter: TypeConverter
          {
              public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
              {
                  return true;
              }
      
              public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
              {
                  if (sourceType == typeof(string))
                      return true;
      
                  return base.CanConvertFrom(context, sourceType);
              }
      
              public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
              {
                  if (value.GetType() == typeof(string))
                  {
                      Type t = Type.GetType((string)value);
      
                      return t;
                  }
      
                  return base.ConvertFrom(context, culture, value);
      
              }
      
              public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
              {
                  var types = new Type[] { 
                      typeof(bool), 
                      typeof(int), 
                      typeof(string), 
                      typeof(float),
                      typeof(Person),
                      typeof(DateTime)};
      
                  TypeConverter.StandardValuesCollection svc =
                      new TypeConverter.StandardValuesCollection(types);
                  return svc;
              }
          }
      
          [Category("Standard")]
          public string Name { get; set; }
          [Category("Standard")]
          public string Desc { get; set; }
      
          [Browsable(false)]
      
          public object DefaultValue { get; set; }
      
          Type type;
      
          [Category("Standard")]
          [TypeConverter(typeof(CustomTypeConverter))]       
          public Type Type
          {
              get
              {
                  return type;
              }
              set
              {
                  type = value;
                  if (value == typeof(string))
                      DefaultValue = "";
                  else
                      DefaultValue = Activator.CreateInstance(value);
              }
          }
      }
      
      static class Program
      {
          [STAThread]
          static void Main()
          {
              Application.EnableVisualStyles();
              var obj = new CustomObjectType
              {
                  Name = "Foo",
                  Properties =
                  {
                      new CustomProperty { Name = "Bar", Type = typeof(int), Desc = "I'm a bar"},
                      new CustomProperty { Name = "When", Type = typeof(DateTime), Desc = "When it happened"},
                  }
              };
              Application.Run(new Form { Controls = { new PropertyGrid { SelectedObject = obj, Dock = DockStyle.Fill } } });
          }
      }
      

      它有效,但是,我发现它是一个相当尴尬的解决方案。 因为我为 Object 提供了 PropertyDescriptor,为 CustomProperty 提供了 CustomPropertyConverter,这两者实际上都没有做任何有意义的事情。 但是我也不能删除它们。

      是否有一种优雅的方式允许根据对象的运行时信息使用适当的编辑器来编辑 Object 类型的属性(如 DefaultValue)?

      【讨论】:

        【解决方案3】:

        要走这条路,您需要为每个属性创建一个自定义 PropertyDescriptor。然后,您将通过自定义 TypeConverter 或(或者)ICustomTypeDescriptor/TypeDescriptionProvider 公开它。示例:

        using System;
        using System.Collections.Generic;
        using System.ComponentModel;
        using System.Windows.Forms;
        [TypeConverter(typeof(CustomObjectType.CustomObjectConverter))]
        public class CustomObjectType
        {
            [Category("Standard")]
            public string Name { get; set; }
            private readonly List<CustomProperty> props = new List<CustomProperty>();
            [Browsable(false)]
            public List<CustomProperty> Properties { get { return props; } }
        
            private Dictionary<string, object> values = new Dictionary<string, object>();
        
            public object this[string name]
            {
                get { object val; values.TryGetValue(name, out val); return val; }
                set { values.Remove(name); }
            }
        
            private class CustomObjectConverter : ExpandableObjectConverter
            {
                public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
                {
                    var stdProps = base.GetProperties(context, value, attributes);
                    CustomObjectType obj = value as CustomObjectType;
                    List<CustomProperty> customProps = obj == null ? null : obj.Properties;
                    PropertyDescriptor[] props = new PropertyDescriptor[stdProps.Count + (customProps == null ? 0 : customProps.Count)];
                    stdProps.CopyTo(props, 0);
                    if (customProps != null)
                    {
                        int index = stdProps.Count;
                        foreach (CustomProperty prop in customProps)
                        {
                            props[index++] = new CustomPropertyDescriptor(prop);
                        }
                    }
                    return new PropertyDescriptorCollection(props);
                }
            }
            private class CustomPropertyDescriptor : PropertyDescriptor
            {
                private readonly CustomProperty prop;
                public CustomPropertyDescriptor(CustomProperty prop) : base(prop.Name, null)
                {
                    this.prop = prop;
                }
                public override string Category { get { return "Dynamic"; } }
                public override string Description { get { return prop.Desc; } }
                public override string Name { get { return prop.Name; } }
                public override bool ShouldSerializeValue(object component) { return ((CustomObjectType)component)[prop.Name] != null; }
                public override void ResetValue(object component) { ((CustomObjectType)component)[prop.Name] = null; }
                public override bool IsReadOnly { get { return false; } }
                public override Type PropertyType { get { return prop.Type; } }
                public override bool CanResetValue(object component) { return true; }
                public override Type ComponentType { get { return typeof(CustomObjectType); } }
                public override void SetValue(object component, object value) { ((CustomObjectType)component)[prop.Name] = value; }
                public override object GetValue(object component) { return ((CustomObjectType)component)[prop.Name] ?? prop.DefaultValue; }
            }
        }
        
        
        public class CustomProperty
        {
            public string Name { get; set; }
            public string Desc { get; set; }
            public object DefaultValue { get; set; }
            Type type;
        
            public Type Type
            {
                get
                {
                    return type;
                }
                set
                {
                        type = value;
                        DefaultValue = Activator.CreateInstance(value);
                }              
            }
        }
        
        static class Program
        {
            [STAThread]
            static void Main()
            {
                Application.EnableVisualStyles();
                var obj = new CustomObjectType
                {
                    Name = "Foo",
                    Properties =
                    {
                        new CustomProperty { Name = "Bar", Type = typeof(int), Desc = "I'm a bar"},
                        new CustomProperty { Name = "When", Type = typeof(DateTime), Desc = "When it happened"},
                    }
                };
                Application.Run(new Form { Controls = { new PropertyGrid { SelectedObject = obj, Dock = DockStyle.Fill } } });
            }
        }
        

        【讨论】:

        • “沿着这条路走” - 有没有更优雅的方式来实现这一目标?
        • @Marc Gravell, CustomObjectType.this[string].set{} 应该是 values[name] = value; 如果要修改属性。很棒的代码,顺便说一句。
        • typeConverter 似乎使对象与 JsonConvert 不兼容,有没有办法解决这个问题或根据需要打开/关闭它?你也是救命稻草,代码很棒
        • @TheLemon 好吧,你可以使用TypeDescriptor.AddProvider/RemoveProvider,但这...有点粗糙,我什至不想开始调试!
        • 太棒了,另一个问题(很抱歉一直困扰您),您的代码适用于枚举,但我无法让属性网格显示默认/预填充值。它保持空白,直到用户选择一个选项
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多