CarlM
CarlM

Reputation: 47

C# WPF Insert an elliptical fill inside a polygon

I have a polygon shape being generated in code from external data and drawn to a Canvas using a PointCollection. On top of that from the user selected point of the polygon I am overlaying a circle (Ellipse) of a user selectable size.

What I need to do next is have the bounds of the circle constrained by the perimeter of the polygon. so that the user can vary the size of the circle to part fill the polygon or fully fill it, if made large enough. With any part of the circle outside of the polygon bounds clipped.

Example

Can any one help me with this? Thank you.

Upvotes: 1

Views: 1162

Answers (1)

Athari
Athari

Reputation: 34275

You need to use a Clip property of your ellipse. You can use your polygon's RenderedGeometry property as the source for the clip geometry. However, considering you position them inside a canvas, you'll need to take their position into consideration.

Let's create a multi-value converter which clips one shape with another:

public class ClipWithConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var clipped = (Shape)values[0];
        var clip = (Shape)values[1];

        var clippedPos = new Point(Canvas.GetLeft(clipped), Canvas.GetTop(clipped));
        var clipPos = new Point(Canvas.GetLeft(clip), Canvas.GetTop(clip));
        var deltaVector = clipPos - clippedPos;

        var geometry = clip.RenderedGeometry.Clone();
        geometry.Transform = new TranslateTransform(deltaVector.X, deltaVector.Y);
        return geometry;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

Now we can use it in XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1">
    <Window.Resources>
        <local:ClipWithConverter x:Key="ClipWithConverter"/>
    </Window.Resources>
    <Canvas>
        <Ellipse x:Name="Ellipse" Canvas.Left="50" Canvas.Top="0"
                Fill="Red" Width="250" Height="250">
            <Ellipse.Clip>
                <MultiBinding Converter="{StaticResource ClipWithConverter}">
                    <Binding ElementName="Ellipse"/>
                    <Binding ElementName="Polygon"/>
                </MultiBinding>
            </Ellipse.Clip>
        </Ellipse>
        <Polygon x:Name="Polygon" Canvas.Left="10" Canvas.Top="10"
                Stroke="Black" StrokeThickness="3"
                Points="100 0, 300 150, 150 300, 0 100"/>
    </Canvas>
</Window>

However, our converter gets called a little too early, when no geometry is rendered yet, so let's add a little hack into constructor:

    public MainWindow()
    {
        InitializeComponent();
        Dispatcher.InvokeAsync(
            BindingOperations.GetMultiBindingExpression(Ellipse, ClipProperty).UpdateTarget,
            DispatcherPriority.Background);
    }

Here's the result:

I'll leave updating binding appropriately as an exircise.

Upvotes: 1

Related Questions