I think the best way is the one taken by Visual Studio itself: Each object describes what it can contain by the types of its properties.
So for Visual Studio I can create a new object that can hold Widgets like this:
[ContentAttribute("Children")]
public class MyObject
{
...
public ICollection<Widget> Children { get ... set ... }
}
the Visual Studio type system does the rest.
Of course there are limitations here that might necessitate some extensions or even another technique. For example, if your object can accept two different types of content that don't share a common base class except for object
, you will have to declare your property as ICollection<object>
in which case your toolbox won't know what it can really take unless you have an additional mechanism.
Many add-on mechanisms could be used for these special cases, for example:
- Allow multiple "content" attributes, for example
ICollection<Widget> WidgetChildren { get ... set ...}; ICollection<Doodad> DoodadChildren { get ... set ... }
- Create an attribute you can apply to the class to give it an object type it can contain, for example
[ContentTypeAllowed(typeof(Widget))] [ContentTypeAllowed(typeof(Doodad))]
where your actual content property is IEnumerable<object>
- Create an attribute that just has a list of class names, for example
[ContentTypesAllowed("Widget,Doodad")]
- Create a method that, if defined in the target object, evaluates a potential content class and returns
true
if it can be a child or false
if not, something like this bool CanAcceptChildType(Type childType) { return type.IsSubclassOf(Widget) && !type==typeof(BadWidget); }
- Create an attribute listing illegal children, for example
[ContentTypesDisallowed("BadWidget")]
- Add attributes or methods to your item class to represent this data, for example
[TargetMustRespondTo(EditCommands.Cut)] public class CutTool { ... }
- Add data to your item class to represent this data, for example
new ToolBoxItem { Name="Widget", AllowedContainers=new[] { typeof(MyObject), typeof(OtherObject) }
- Combinations of the above
All of these are viable techniques. Consider your own situation to see which one(s) make the most sense to implement.
To actually implement hiding your tool box item, just use a IMultiValueConverter
bound to a Visibilty property in your tool box item's DataTemplate
. Pass two bindings to the converter: your tool box item and your target. Then implement the logic you have decided on in your IMultiValueConverter
.
For example, in the simplest case where you only care about the collection type of the ContentAttribute, this code would work:
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;
}
In real situations things may be a little more complex, for example not all Content properties are ICollection, so you'll have to do additional checking and maybe implement more algorithms. It would also be a good idea to add some caching so you aren't using reflection as frequently.