How to draw WPF polygon with holes?

I have a collection of custom "PlanarMember" objects displaying Boundary and Openings properties. The Boundary property is a normal "Polygon" class that displays vertices and segments, and so on. Openings is IEnumerable, basically describing holes in a larger polygon. I can guarantee that the holes do not intersect / intersect, the outer boundary is non-self-intersecting and closed, the holes are completely contained by the outer boundary and other reasonable constraints.

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

If that helps, some examples of what I would like to present include a wall with window and door openings, a floor slab with walkways, etc. I also can't just draw solid filled shapes on the larger shape to represent holes because the objects behind have to show through the holes.

+2


source to share


1 answer


After spending some time with this experiment, I came up with a solution that suits my needs. I'll post it here for posterity. Hopefully this saves someone else 6 hours or so I wasted with this problem.

A couple of classes I am using that are defined elsewhere:

  • PlanarElement : simply describes a 2d "thing" with a Boundary property representing a Polygon property and Openings displaying a list of Polygon IEnumerable objects.
  • Polygon : a 2d planar geometry class that displays an IEnumerable property of type Point
  • Point : 3D geometric point

Here's the solution:

1) Here is the ItemsControl that my PlanarMembers will be associated with



<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) Note the use of the PlanarMemberConverter in the Path Data setting created here:

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

      

3) Here's the actual derived IValueConverter class defined:

[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);
}
}

      

+1


source







All Articles