在VB6, VC++, C#.net 里都可以见到一个属性设计器,用来编辑修改 object 的属性。C# 下提供了一个属性设计器 PropertyGrid, 其使用极其简单,只要
grid.SelectedObject = myOjbect;
就可以把myOjbect 的所有属性显示出来。不过很多时候我们不希望如此,因为欠缺一种灵活性。我希望可以自由的控制需要编辑的内容。能做的这一点,再配合可编辑的 ListView, 可以很好的解决复杂内容的修改编辑问题。
首先是定义一个CProperty, 用来设置 object 在PropertyGrid 中的内容, 还得定义一个实现 ICustomTypeDescriptor 接口的容器CPropertyCollection, 最后定制一个 PropertyDescriptor, 用来描述 PropertyGrid 接口方法。
public class CPropertyCollection : CCollection<CProperty>, ICustomTypeDescriptor
{
public void Add(CProperty value)
{
base.Put(value.Name, value);
}
#region "TypeDescriptor"
public String GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public String GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptor[] propDes = new PropertyDescriptor[this.Count];
for (int i = 0; i < this.Count; i++) {
CProperty prop = (CProperty) this[i];
propDes[i] = new CPropertyDescriptor(ref prop, attributes);
}
return new PropertyDescriptorCollection(propDes);
}
public PropertyDescriptorCollection GetProperties()
{
return TypeDescriptor.GetProperties(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
}
public class CProperty
{
private string m_Name = string.Empty;
private bool m_bReadOnly = false;
private bool m_bVisible = true;
private object m_Value = null;
private string m_Category = string.Empty;
TypeConverter m_Converter = null;
object m_Editor = null;
public CProperty(string name, object value)
{
m_Name = name;
m_Value = value;
}
public CProperty(string name, object value, bool bReadOnly, bool bVisible)
{
m_Name = name;
m_Value = value;
m_bReadOnly = bReadOnly;
m_bVisible = bVisible;
}
public bool ReadOnly
{
get { return m_bReadOnly; }
set { m_bReadOnly = value; }
}
public virtual TypeConverter Converter
{
get { return m_Converter; }
set { m_Converter = value; }
}
public string Name
{
get { return m_Name; }
set { m_Name = value; }
}
public bool Visible
{
get { return m_bVisible; }
set { m_bVisible = value; }
}
public virtual object Value
{
get { return m_Value; }
set { m_Value = value; }
}
public string Category
{
get { return m_Category; }
set { m_Category = value; }
}
public virtual object Editor
{
get { return m_Editor; }
set { m_Editor = value; }
}
}
public class CPropertyDescriptor : PropertyDescriptor
{
CProperty m_Property;
public CPropertyDescriptor(ref CProperty property, Attribute[] attrs)
: base(property.Name, attrs)
{
m_Property = property;
}
#region PropertyDescriptor "region"
public override bool CanResetValue(object component)
{
return false;
}
public override Type ComponentType
{
get { return null; }
}
public override object GetValue(object component)
{
return m_Property.Value;
}
public override string Description
{
get { return m_Property.Name; }
}
public override string Category
{
get { return m_Property.Category; }
}
public override string DisplayName
{
get { return m_Property.Name; }
}
public override bool IsReadOnly
{
get { return m_Property.ReadOnly; }
}
public override TypeConverter Converter
{
get { return m_Property.Converter; }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override void SetValue(object component, object value)
{
m_Property.Value = value;
}
public override Type PropertyType
{
get { return m_Property.Value.GetType(); }
}
public override object GetEditor(Type editorBaseType)
{
return m_Property.Editor == null ? base.GetEditor(editorBaseType) : m_Property.Editor;
}
#endregion
}
下面的事情变得很简单:
private void Form_Load(object sender, EventArgs e)
{
CProperty myProp1 = new CProperty("Test1", "test");
MyProp1.Category = "test";
CProperty myProp2 = new CProperty("Test2", 1);
myProp2.Editor = new System.Drawing.Design.ColorEditor();
myProperties.Add(myProp1);
myProperties.Add(myProp2);
grid.SelectedObject = myProperties;
}
可以看到通过CProperty.Editor 可以使用各种的编辑器,甚至自定义的编辑器(从 UITypeEditor 派生,msdn 中有例子)。另一个要点是CProperty.Converter, 用来自定义如何进行类型转换,例如,enum 类型在PropertyGrid 中用 List 编辑,如果不想事先定义一个 enum, 可以用自己的 TypeConverter 来实现功能更强大的编辑方法。
public class ListConverter : StringConverter
{
object[] m_Objects;
public ListConverter(object[] objects)
{
m_Objects = objects;
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override
System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(m_Objects);
}
}
使用ListConverter:
CProperty myProp = new CProperty("Test", "test2");
myProp.Converter = new ListConverter(new string[] { "test1", "test2", "test3" });
当click 属性 Test 时,会弹出一个 List 框。重载 TypeConverter 的 ConvertTo and ConvertFrom 可以获得更多的功能。