views:

641

answers:

1

I have a collection of custom "PlanarMember" objects exposing Boundary and Openings properties. The Boundary property is a custom "Polygon" class exposing vertices and segments, etc. The Openings is an IEnumerable basically describing openings in the larger polygon. I can guarantee that the openings do not overlap/intersect, the outer boundary is non-self intersecting and closed, the openings are fully contained by the outer boundary and other reasonable limitations.

I want to create a WPF binding to allow me to throw these objects into a ItemsControl collection with a custom ItemsPanel of Canvas, that basically means I need to be able to convert my custom PlanarMember object into a WPF object that can be placed on a Canvas and look like a solid filled polygon with openings. This library is used in other contexts, so using a WPF class instead of the original PlanarMember is out of the question. It would be trivial to just represent the outer boundary as a WPF Polygon, but it's the openings that are throwing me. Any thoughts?

If it helps, some examples of what I'd be trying to represent include a wall with window and door openings, a floor slab with penetrations, etc. I also can't just draw solid filled shapes on the larger shape to represent openings because the objects behind need to show through the holes.

+1  A: 

After experimenting with this for a while, I've come up with a solution that satisfies my needs. I'll post it here for posterity. Hopefully it saves someone else the 6 hours or so I've spent on this issue.

Couple classes I use that are defined elsewhere:

  • PlanarElement: Simply describes a 2d "thing" with a Boundary property exposing a Polygon and an Openings property exposing an IEnumerable list of Polygon objects
  • Polygon: 2d planar geometric class exposing a property of type IEnumerable of type Point
  • Point: 3d geometric point


Here is the solution:

1) Here is the ItemsControl to which my PlanarMembers will be bound

<ItemsControl>
<ItemsControl.Resources>
    <DataTemplate DataType="{x:Type o:PlanarMember}">
        <Path Stroke="Black" StrokeThickness="1" 
              Data="{Binding Converter={StaticResource PlanarMemberConverter}}">
            <Path.Fill>
                <SolidColorBrush Opacity="0.5" Color="LightGray" />
            </Path.Fill>
        </Path>
    </DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <Canvas RenderTransform="{StaticResource CurrentDisplayTransform}"/>
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

2) Notice the use of PlanarMemberConverter in the Path's Data setting, created here:

<this:PlanarMemberConverter x:Key="PlanarMemberConverter" />

3) The actual IValueConverter derived class is defined here:

[ValueConversion(typeof(PlanarMember), typeof(Geometry))]
class PlanarMemberConverter : IValueConverter
{

#region IValueConverter Members

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    if (targetType != typeof(Geometry)) return null;
    var planarMember = value as PlanarMember;

    var collection = new PathFigureCollection(planarMember.Openings.Count() + 1)
                         {
                             new PathFigure(
                                 planarMember.Boundary.Vertices.First().AsPoint(),
                                 ToSegments(planarMember.Boundary.Vertices), true){IsFilled = true}
                         };

    foreach (var opening in planarMember.Openings)
    {
        collection.Add(new PathFigure(
                           opening.Vertices.First().AsPoint(),
                           ToSegments(opening.Vertices), true) { IsFilled = true });
    }

    return new PathGeometry(collection) {FillRule = FillRule.EvenOdd};
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    throw new NotImplementedException();
}

#endregion

public IEnumerable<PathSegment> ToSegments(IEnumerable<Point> points)
{
    return
        from point in points
         select (PathSegment)new LineSegment(point.AsPoint(), true);
}
}
MKing