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;
在网上搜索了如何执行此操作,我设法拼凑了一个最小的工作示例,但我不太了解它是如何工作的。要复制,它有一个单一的形式,在 (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;