Holes

How do you make a hole in a visual? That is the question. It appears that there are four ways to do so, as follow:

One hole, it seems, isn't too onerous. Two holes, on the other hand is today's headache.

CombinedGeometry

A CombinedGeometry allows only two geometries to be combined. Further holes are created by nesting further

    <Canvas Background="OldLace">
        <Path Fill="LightSkyBlue" Stroke="Black">
            <Path.Data>
                <CombinedGeometry GeometryCombineMode="Exclude">
                    <CombinedGeometry.Geometry1>
                        <EllipseGeometry Center="60,50" RadiusX="50" RadiusY="30" />
                    </CombinedGeometry.Geometry1>
                    <CombinedGeometry.Geometry2>
                        <CombinedGeometry GeometryCombineMode="Union">
                            <CombinedGeometry.Geometry1>
                                <EllipseGeometry Center="40,50" RadiusX="20" RadiusY="20" />
                            </CombinedGeometry.Geometry1>
                            <CombinedGeometry.Geometry2>
                                <EllipseGeometry Center="80,45" RadiusX="10" RadiusY="10" />
                            </CombinedGeometry.Geometry2>
                        </CombinedGeometry>
                    </CombinedGeometry.Geometry2>
                </CombinedGeometry>
            </Path.Data>
        </Path>
    </Canvas>

If you want to make a wheel with five holes in, you need 5 nested combined geometries.

The CombinedGeometry allows two geometries to be combined according to the usual combination suspects using the GeometryCombineMode property:

GeometryGroup

The GeometryGroup allows more than two geometries to be combined. There are only two CombineModes

<Path Stroke="Black" StrokeThickness="1" Fill="Orange">
            <Path.Data>
                <!-- Creates a composite shape from three geometries. -->
                <GeometryGroup FillRule="EvenOdd">
                    <EllipseGeometry Center="60,150" RadiusX="50" RadiusY="30" />
                    <RectangleGeometry Rect="70,140 40 30" />
                    <EllipseGeometry Center="40,140" RadiusX="10" RadiusY="10"/>
                </GeometryGroup>
            </Path.Data>
        </Path>
        <Path Stroke="Black" StrokeThickness="1" Fill="Orange">
            <Path.Data>
                <!-- Creates a composite shape from three geometries. -->
                <GeometryGroup FillRule="Nonzero">
                    <EllipseGeometry Center="200,150" RadiusX="50" RadiusY="30" />
                    <RectangleGeometry Rect="210,140 40 30" />
                    <EllipseGeometry Center="180,140" RadiusX="10" RadiusY="10"/>
                </GeometryGroup>
            </Path.Data>
        </Path>

The geometrygroup does not allow us to exclude, so does not prove as useful as the CombinedGeometry when animating the hole and shape seperately, as a hole moving beyond the edge of the shape will begin to be filled.

Clip

A clip, in GDI+ was only semi useful as the clip was not anti-aliased. However there was an exclude mode, so the clip could draw either only inside, or only outside the clipping region.

The new clip only allows drawing within the clipping region, so is of little use making holes.

        <Ellipse Width="100" Height="70" Canvas.Left="300" Canvas.Top="120" Fill="#FFFF0000" Stroke="#FF000000">
            <Ellipse.Clip>
                <EllipseGeometry RadiusX='50' RadiusY='50' Center='10,0'/>
            </Ellipse.Clip>
        </Ellipse>

OpacityMask

I can't say I like how inordinately complex it is to use the OpacityMask. First, the dimensions of the shapes used in the mask are relative to the element, in units of 0 to 100%, so we cannot use this either to move holes beyond the edge of the shape as the coordinate systems are different. If a shape's height is changed, the hole's height will change too.

        <Ellipse Width="100" Height="100" Canvas.Left="450" Canvas.Top="100" Fill="Blue">
            <Ellipse.OpacityMask>
                <DrawingBrush>
                    <DrawingBrush.Drawing>
                        <GeometryDrawing>
                            <GeometryDrawing.Brush>
                                <SolidColorBrush Color="Black"></SolidColorBrush>

                            </GeometryDrawing.Brush>
                            <GeometryDrawing.Geometry>
                                <GeometryGroup FillRule="EvenOdd" >
                                    <RectangleGeometry Rect="0,0,100,100" />
                                    <EllipseGeometry RadiusX="10" RadiusY="10" Center="70,70" />
                                    <EllipseGeometry RadiusX="10" RadiusY="10" Center="70,30" />
                                    <EllipseGeometry RadiusX="10" RadiusY="10" Center="30,70" />
                                    <EllipseGeometry RadiusX="10" RadiusY="10" Center="30,30" />
                                </GeometryGroup>

                            </GeometryDrawing.Geometry>
                            <GeometryDrawing.Pen>
                                <Pen Thickness="0" Brush="Black" />
                            </GeometryDrawing.Pen>
                        </GeometryDrawing>
                    </DrawingBrush.Drawing>
                </DrawingBrush>

            </Ellipse.OpacityMask>
        </Ellipse>

Update - the many hole solution

Well, after ruminating over other things for a while, I finally came up with the answer. My IQ of 4, ably supported by my doggedness quotient of 0.27, and further supported by a good night's sleep, provided the answer on awakening this morning. It's so obvious, I can't see how I didn't think of it before. The way to punch many holes in a single object is to use a combination of the combinations. In short, use a GeometryGroup as the second geometry in a CombinedGeometry, as follows:

        <Path Fill="LightSkyBlue" Stroke="Black">
            <Path.Data>
                <CombinedGeometry GeometryCombineMode="Exclude">
                    <CombinedGeometry.Geometry1>
                        <EllipseGeometry Center="650,150" RadiusX="70" RadiusY="50" />
                    </CombinedGeometry.Geometry1>
                    <CombinedGeometry.Geometry2>
                        <GeometryGroup FillRule="NonZero" >
                            <RectangleGeometry Rect="0,0,100,100" />
                            <EllipseGeometry RadiusX="20" RadiusY="20" Center="630,120" />
                            <EllipseGeometry RadiusX="20" RadiusY="20" Center="620,180" />
                            <RectangleGeometry Rect="660,150,40,30" />
                            <EllipseGeometry RadiusX="20" RadiusY="10" Center="660,130" />
                        </GeometryGroup>
                    </CombinedGeometry.Geometry2>
                </CombinedGeometry>
            </Path.Data>
        </Path>

Now for the Swiss cheese.