我认为最好的方法是 Visual Studio 本身采用的方法:每个对象通过其属性的类型来描述它可以包含的内容。
所以对于 Visual Studio,我可以创建一个新的对象来保存这样的小部件:
[ContentAttribute("Children")]
public class MyObject
{
...
public ICollection<Widget> Children { get ... set ... }
}
Visual Studio 类型系统完成其余的工作。
当然,这里有一些限制,可能需要一些扩展甚至另一种技术。例如,如果您的对象可以接受两种不同类型的内容,除了 之外,它们不共享一个公共基类object
,您将必须声明您的属性,因为ICollection<object>
在这种情况下,您的工具箱将不知道它可以真正接受什么,除非您有一个额外的机制。
许多附加机制可用于这些特殊情况,例如:
- 允许多个“内容”属性,例如
ICollection<Widget> WidgetChildren { get ... set ...}; ICollection<Doodad> DoodadChildren { get ... set ... }
- 创建一个可以应用于该类的属性,以赋予它可以包含的对象类型,例如
[ContentTypeAllowed(typeof(Widget))] [ContentTypeAllowed(typeof(Doodad))]
您的实际内容属性在哪里IEnumerable<object>
- 创建一个只有类名列表的属性,例如
[ContentTypesAllowed("Widget,Doodad")]
- 创建一个方法,如果在目标对象中定义,则评估潜在的内容类并返回
true
它是否可以是子对象,false
如果不是,类似这样bool CanAcceptChildType(Type childType) { return type.IsSubclassOf(Widget) && !type==typeof(BadWidget); }
- 创建一个列出非法儿童的属性,例如
[ContentTypesDisallowed("BadWidget")]
- 将属性或方法添加到您的项目类以表示此数据,例如
[TargetMustRespondTo(EditCommands.Cut)] public class CutTool { ... }
- 将数据添加到您的项目类以表示此数据,例如
new ToolBoxItem { Name="Widget", AllowedContainers=new[] { typeof(MyObject), typeof(OtherObject) }
- 以上的组合
所有这些都是可行的技术。考虑您自己的情况,看看哪些最适合实施。
要真正实现隐藏工具箱项,只需IMultiValueConverter
在工具箱项的DataTemplate
. 将两个绑定传递给转换器:您的工具箱项和您的目标。然后在IMultiValueConverter
.
例如,在您只关心 ContentAttribute 的集合类型的最简单情况下,此代码将起作用:
public object Convert(object[] values, ...)
{
var toolItem = values[0] as ToolItem;
var container = values[1];
var contentPropertyAttribute =
container.GetType().GetCustomAttributes()
.OfType<ContentPropertyAttribute>()
.FirstOrDefault();
if(contentPropertyAttribute!=null)
{
var contentProperty = container.GetType().GetProperty(contentPropertyAttribute.Name);
if(contentProperty!=null &&
contentProperty.Type.IsGeneric &&
contentProperty.Type.GetGenericArguments()[0].IsAssignableFrom(toolItem.Type))
return Visibility.Visible;
}
return Visibility.Collapsed;
}
在实际情况下,事情可能会稍微复杂一些,例如并非所有的 Content 属性都是 ICollection,因此您必须进行额外的检查并可能实现更多算法。添加一些缓存也是一个好主意,这样您就不会经常使用反射。