C# 中 PropertyGrid 的通用动态列表

General purpose dynamic list for a PropertyGrid in C#

在网上搜索了如何执行此操作,我设法拼凑了一个最小的工作示例,但我不太了解它是如何工作的。要复制,它有一个单一的形式,在 (Form1, 属性Grid1) 上有一个 属性 网格。有一个 class Clothing 对象的实例,它被指定为 PropertyGrid 的 SelectedObject。有两个属性需要仅在运行时才知道的列表。它们将由通用 class: StringListConverter.

返回

所以,代码:

Form1.cs:

    public partial class Form1 : Form
    {
        Clothing obj = new Clothing();
        
        public Form1()
        {
            InitializeComponent();
            propertyGrid1.SelectedObject = obj;
        }
    }

Clothing.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Reflection;
using System.ComponentModel;

namespace PropertyGrid2
{
    public class Clothing
    {
        private string _name = "Shirt";
        private string _clothingSize = "M";
        private string _supplier = "Primark";

        [TypeConverter(typeof(StringListConverter))]
        public string ClothingSize
        {
            get { return _clothingSize; }
            set { _clothingSize = value; }
        }
        
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        [TypeConverter(typeof(StringListConverter))]
        public string Supplier
        {
            get { return _supplier; }
            set { _supplier = value; }
        }
        
    }
}

StringListConverter.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.ComponentModel;

namespace PropertyGrid2
{
    public class StringListConverter : TypeConverter
    {
        private List<string> _sizes;
        private List<string> _suppliers;

        public StringListConverter()
            : base()
        {
            _sizes = new List<string>();
            _sizes.Add("XS");
            _sizes.Add("S");
            _sizes.Add("M");
            _sizes.Add("L");
            _sizes.Add("XL");

            _suppliers = new List<string>();
            _suppliers.Add("Primark");
            _suppliers.Add("M&S");
            _suppliers.Add("Sports Direct");
        }
        
        public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
        {
            return true;
        }
        
        public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
        {
            switch (context.PropertyDescriptor.Name)
            {
                case "ClothingSize": return new StandardValuesCollection(_sizes);
                case "Supplier": return new StandardValuesCollection(_suppliers);
                default: throw new IndexOutOfRangeException();
            }
        }
    }
}

在上面显示的示例中,我必须在 StringListConverter 的构造函数中实例化 _sizes 和 _suppliers 的内容。但是,我非常喜欢添加方法 Add、Count、Items、Remove,以便它是通用的并且可同时用于同一对象上的多个属性。我想为每个列表创建一个 class 实例并加载该列表的项目(因此名称为 StringListConverter)。我目前在上面要做的是在 class 中加载多个列表,然后让它理解从中为其 属性.

选择项目的对象

例如,如果我在 Clothing 上有其他属性,如“Fit”或“PairedItem”,我可以使用相同的 class,创建它的实例并用适当的列表填充它们,然后附加它们到适当的属性。

所以,我的问题是:

我怎样才能使 StringListConverter 真正通用,只包含一个列表,为不同的属性提供它的不同实例,并摆脱破坏封装的 switch 语句?它不需要知道从哪里调用它。

ITypeDescriptorContext.Instance Property will contain the instance of the object selected in the property grid (here a Clothing object) and ITypeDescriptorContext.PropertyDescriptor Property 将包含 属性 描述符(此处为 ClothingSize

如果模型已经知道哪个列表将用于 属性 可以吗?如果是,那么:

给你的StringListConverter一个类型参数。那里指定的类型将负责提供列表。

public interface IValueListSupplier
{
    IEnumerable<string> GetValues();
}

 public class StringListConverter<VALUES> : TypeConverter where VALUES : IValueListSupplier, new()
{

    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
    {
        return true;
    }

    public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
    {
        VALUES values = new VALUES();
        return new StandardValuesCollection(values.GetValues().ToList());
    }
}

然后您可以创建特定类型来表示某些列表并在您的模型中使用它们:

public class SizeValues : IValueListSupplier
{
    public IEnumerable<string> GetValues()
    {
        var values = new List<string>();
        // Or load from database here
        values.Add("XS");
        values.Add("S");
        values.Add("M");
        values.Add("L");
        values.Add("XL");
        return values;
    }
}

public class SupplierValues : IValueListSupplier
{
    public IEnumerable<string> GetValues()
    {
        var values = new List<string>();
        // Or load from database here
        values.Add("Primark");
        values.Add("M&S");
        values.Add("Sports Direct");
        return values;
    }
}

public class Clothing
{
    private string _name = "Shirt";
    private string _clothingSize = "M";
    private string _supplier = "Primark";

    [TypeConverter(typeof(StringListConverter<SizeValues>))]
    public string ClothingSize
    {
        get { return _clothingSize; }
        set { _clothingSize = value; }
    }

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    [TypeConverter(typeof(StringListConverter<SupplierValues>))]
    public string Supplier
    {
        get { return _supplier; }
        set { _supplier = value; }
    }

}

另一种方法是让您的 StringListConverter 允许业务代码为某个 class 的某个 属性 注册列表,如下所示:

public class StringListConverter : TypeConverter 
{
    /// <summary>
    /// Dictionary that maps a combination of type and property name to a list of strings
    /// </summary>
    private static Dictionary<(Type type, string propertyName), IEnumerable<string>> _lists = new Dictionary<(Type type, string propertyName), IEnumerable<string>>();

    public static void RegisterValuesForProperty(Type type, string propertyName, IEnumerable<string> list)
    {
        _lists[(type, propertyName)] = list;
    }

    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
    {
        return true;
    }

    public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
    {
        if (_lists.TryGetValue((context.PropertyDescriptor.ComponentType, context.PropertyDescriptor.Name), out var list))
        {
            return new StandardValuesCollection(list.ToList());
        }
        else
        {
            throw new Exception("Unknown property " + context.PropertyDescriptor.ComponentType + " " + context.PropertyDescriptor.Name);
        }

        
    }
}

用法:

var values = new List<string>();
values.Add("XS");
values.Add("S");
values.Add("M");
values.Add("L");
values.Add("XL");
StringListConverter.RegisterValuesForProperty(typeof(Clothing), nameof(Clothing.ClothingSize), values);

values = new List<string>();
values.Add("Primark");
values.Add("M&S");
values.Add("Sports Direct");
StringListConverter.RegisterValuesForProperty(typeof(Clothing), nameof(Clothing.Supplier), values);

Clothing obj = new Clothing();
propertyGrid1.SelectedObject = obj;