Question

Scenario: I have a range of custom UIElements (in fact, I have replaced all the standard WPF FrameworkElements I would use with lighter, more efficient counterparts) for a custom layout system intended to only use those. They all inherit from a class called Surface (which in turn is a direct descendant of UIElement).

I am now wondering if my version of Panel (let's call it SurfacePanel) can simply implement IList<Surface> and allow child Surface elements to be added directly to it, rather than to a Children property (as with regular WPF panels), in XAML.

To illustrate - in codebehind, I can do now this:

SurfacePanel.Add(child);

And from that, I would like to be able to do this in XAML:

<SurfacePanel>
    <child />
</SurfacePanel>

But XAML seems to require me to have a codebehind pattern like this:

SurfacePanel.Children.Add(child)

(I don't really need these controls to support XAML to work in the runtime environment, but when testing and prototyping, I like to make my UI controls "XAML friendly" so I can benefit from the visual designer in VS (along with the property pane etc), if nothing more than as a 'preview' window).

Since my controls inherit from UIElement (and have the proper Measure/Arrange/Render overrides and so on), they function quite well when put on, say, a regular Canvasor Grid. But the VS XAML parser is not too happy about my SurfacePanel (that implements IList<Surface>) when I am adding children to it in markup. It says "Cannot add content to an object of type "SurfacePanel"".

I know that if I add a Children property of an appropriate type and add an attribute to the SurfaceCanvas class ([ContentProperty("Children")]), it will work. But since the SurfacePanel is itself a collection capable of the same thing, is there a way to make XAML 'get it'?


Edit:

I can solve the XAML 'compliance' by adding a Children property on the SurfacePanel that simply returns its inner List, but then adding and removal of elements on that directly bypasses the internal logic that wire the child elements up.

If the inner list was an ObservableCollection, I could do it the conventional way and do the wiring in a CollectionChanged event handler - but basically the whole point of integrating IList in the Panel directly is to avoid that..


Edit 2:

This "works" (but bypasses the wiring):

[ContentProperty("Children")]
public class SurfacePanel : Surface, IList<Surface>
{
    private readonly List<Surface> _children = new List<Surface>(); 

    public List<Surface> Children
    {
        get { return _children; }
    }
}

I cannot return this because SurfacePanel is not a List<Surface>, but an IList<Surface>.

If I change the property to

    public IList<Surface> Children
    {
        get { return this; }
    }

I get an error message even with the following XAML (but not with <m:SurfacePanel/>):

<m:SurfacePanel>
</m:SurfacePanel>

The error message is

Cannot set content property 'Children' on element 'SurfacePanel'. 'Children' has incorrect access level or its assembly does not allow access.

Was it helpful?

Solution

Also implement IList and declare the Children property like this:

[ContentProperty("Children")]
public class SurfacePanel : Surface, IList, IList<Surface>
{
    public IList Children
    {
        get { return this; }
    }

    ...
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top