Search code examples
c#wpfxamlcanvasscaling

WPF: Scale a shape and its Bounding Box to fit another Canvas - without affecting Stroke Thickness


I have some shapes described in XAML, mostly curved lines. Some of them have padding around the edges, achieved by placing them in a Canvas larger than the shape. This Canvas acts as a bounding box.

For example, this shape appears like this in Designer. The shape is the curved line. The outer square is the bounding Canvas, indicated by red arrows.

<Canvas Height="160"
        Width="160"
        Background="Red">
  <Path Stroke="#000000"
        Data="M27.9,108 c0,0 25,-63 48.5,-51.5 s34.5,64.5 68.5,37">
  </Path>
</Canvas>

enter image description here

Now I need to scale the Canvas and its shape to dynamically fit a larger Canvas, while maintaining the shape's Geometry relative to its bounding Canvas, and also while maintaining the Stroke.Thickness.

This is how the shape appears without scaling (bounding Canvas is red, CanvasI need to scale to is yellow):

enter image description here

This how it needs to look after scaling, but without changing the Stroke.Thickness. Here I'm using a ViewBox:

enter image description here

Is there a solution to this problem that I am overlooking?

I suspect the only solution involves setting the shape's Path.Stretch to Fill, changing its Height and Width, and then applying a Margin to the bounding Canvas to replicate the padding, which sounds quite horrible.


Solution

  • You can simply set the Path.Stretch property to Fill and the geometry will automatically scale to the size of the shape. Unfortunately, when the geometry is stretched, it is also "trimmed" to a minimal bounding box, so you'll loose the margins around the geometry, but you can always set Path.Margin to desired values. Also, you should drop the wrapping Canvas altogether.

    However, judging by the image with desired effect you also want the margin to scale together with the geometry itself. In this case setting Path.Margin won't cut the mustard - the margins will remain "constant". Fortunately, there's a simple trick that will solve this problem - all you need to do is to incorporate the margins in the geometry itself. To do that, you simply need to add two "empty" moves to the geometry, which would define two opposite points of the desired bounding box. In your case, if you want the shape to be nominally of size 160x160, and scale automatically to fit the available space, all you need is this:

    <Path Stroke="#000000"
          Data="M0,0 M160,160 M27.9,108 c0,0 25,-63 48.5,-51.5 s34.5,64.5 68.5,37"
          Stretch="Fill" />
    

    Path does not have a Background property though, so you might want to wrap it with a Border after all.