在为产品原型设计新功能时,意识到展示如何使用TypeDescriptionProvider属性及其相关类会是一个好主意。本文将通过一个示例演示应用程序来说明这一点,该应用程序包含一个对象列表和一个显示所选对象属性的属性网格。在这个演示中,假设始终有两种类型的对象——一种代表书籍,另一种代表电影。这两种对象之间唯一的共同属性是Name属性。
书籍对象可能包含以下属性:
电影对象可能包含以下属性:
为了支持新对象类型的添加以及现有对象类型的属性动态添加(包括运行时),不能仅仅依赖于频繁添加新类或修改现有类。这时,使用TypeDescriptionProvider就显得非常有用。本文的剩余部分将解释如何使用这种方法来实现需求。
首先,实现一个枚举来表示对象的类型。当添加新的对象类型时,会将其添加到这个枚举中。
public enum TitleCategory {
Book,
Movie
}
接下来,添加一个名为Title的密封类,它定义了类型描述符提供者,并提供了默认属性。
[TypeDescriptionProvider(typeof(TitleTypeDescriptionProvider))]
sealed class Title {
public Title(String name, TitleCategory category) {
this.Name = name;
this.Category = category;
}
public String Name { get; set; }
[Browsable(false)]
public TitleCategory Category { get; private set; }
public override string ToString() {
return Name;
}
private Dictionary customFieldValues = new Dictionary();
public Object this[String fieldName] {
get {
Object value = null;
customFieldValues.TryGetValue(fieldName, out value);
return value;
}
set {
customFieldValues[fieldName] = value;
}
}
}
除了Name属性外,还有一个Category属性,用于指定对象的类型。此外,还会看到有一个字典用于自定义属性值,以及一个索引器,可用于获取和设置任何对象上的自定义属性。注意类上的TypeDescriptionProvider属性,它指定了TitleTypeDescriptionProvider作为类型描述符提供者。
现在,可以编写TitleTypeDescriptionProvider类:
class TitleTypeDescriptionProvider : TypeDescriptionProvider {
private static TypeDescriptionProvider defaultTypeProvider = TypeDescriptor.GetProvider(typeof(Title));
public TitleTypeDescriptionProvider() : base(defaultTypeProvider) { }
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) {
ICustomTypeDescriptor defaultDescriptor = base.GetTypeDescriptor(objectType, instance);
return instance == null ? defaultDescriptor : new TitleCustomTypeDescriptor(defaultDescriptor, instance);
}
}
这里的重要方法是GetTypeDescriptor重写。当实例为null时返回默认描述符,否则返回一个TitleCustomTypeDescriptor对象。TitleCustomTypeDescriptor编写如下:
class TitleCustomTypeDescriptor : CustomTypeDescriptor {
public TitleCustomTypeDescriptor(ICustomTypeDescriptor parent, object instance) : base(parent) {
Title title = (Title)instance;
customFields.AddRange(CustomFieldsGenerator.GenerateCustomFields(title.Category).Select(f => new CustomFieldPropertyDescriptor(f)).Cast());
}
private List customFields = new List();
public override PropertyDescriptorCollection GetProperties() {
return new PropertyDescriptorCollection(base.GetProperties().Cast().Union(customFields).ToArray());
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) {
return new PropertyDescriptorCollection(base.GetProperties(attributes).Cast().Union(customFields).ToArray());
}
}
这个类的核心是根据Category属性为对象生成自定义字段的列表。然后,在GetProperties重写中,将这些属性(字段)添加到默认列表中。这里的设计动态性得以体现,它允许动态地向对象类型添加新属性。对于演示,有一个CustomFieldsGenerator类,它返回动态字段。
CustomFieldsGenerator类如下所示:
internal static IEnumerable GenerateCustomFields(TitleCategory category) {
List customFields = new List();
switch (category) {
case TitleCategory.Book:
customFields.Add(new CustomField("Author", typeof(String)));
customFields.Add(new CustomField("HardCover", typeof(bool)));
customFields.Add(new CustomField("Amazon Rank", typeof(int)));
break;
case TitleCategory.Movie:
customFields.Add(new CustomField("Director", typeof(String)));
customFields.Add(new CustomField("Rating", typeof(MovieRating)));
customFields.Add(new CustomField("Duration", typeof(TimeSpan)));
customFields.Add(new CustomField("Release Date", typeof(DateTime)));
break;
}
return customFields;
}
每个字段都使用CustomFieldPropertyDescriptor类存储,该类负责建立自定义属性的行为。
CustomFieldPropertyDescriptor类如下所示:
class CustomFieldPropertyDescriptor : PropertyDescriptor {
public CustomField CustomField { get; private set; }
public CustomFieldPropertyDescriptor(CustomField customField) : base(customField.Name, new Attribute[0]) {
CustomField = customField;
}
public override bool CanResetValue(object component) {
return false;
}
public override Type ComponentType {
get {
return typeof(Title);
}
}
public override object GetValue(object component) {
Title title = (Title)component;
return title[CustomField.Name] ?? (CustomField.DataType.IsValueType ? (Object)Activator.CreateInstance(CustomField.DataType) : null);
}
public override bool IsReadOnly {
get {
return false;
}
}
public override Type PropertyType {
get {
return CustomField.DataType;
}
}
public override void ResetValue(object component) {
throw new NotImplementedException();
}
public override void SetValue(object component, object value) {
Title title = (Title)component;
title[CustomField.Name] = value;
}
public override bool ShouldSerializeValue(object component) {
return false;
}
}
这里的重要方法是GetValue和SetValue重写。使用Title类的索引器来实现这两种方法。注意,如果Title对象上尚未设置动态字段,则还会根据对象的类型返回一个默认值。还请注意PropertyType属性——这使能够为特定字段指定确切的数据类型。例如,电影评级是一个名为MovieRating的枚举类型,属性网格会尊重这一点。
enum MovieRating {
G,
PG,
PG13,
R,
NC17
}
这就是全部内容。不仅仅是属性网格会尊重类型描述符,任何支持数据绑定的控件都会如预期工作。因此,可以将此传递给数据网格视图或第三方数据可视化控件,一切都会正常工作。请注意,在代码中,不能以常规方式访问动态属性,而必须通过索引器访问,如下所示:
Title title = new Title(name, TitleCategory.Movie);
title["Director"] = director;
title["Rating"] = rating;
title["Duration"] = duration;
title["Release Date"] = releaseDate;