Keeping Canvas (or any other Element) from rendering outside its Bounds in UWP

Canvas is a panel that enables absolute layouting. It therefore renders its children, per default, even if they are outside of its dimensions, as indicated by the documentation:

The children of a Canvas (if any) are still visible even if the Canvas has any of these conditions:
● Height or Width property of the Canvas is 0.

Sometimes this is not the desired behavior. For example, if the canvas is not the only UI element in your application, it would be pretty annoying if its children would overlap the other (more important) elements (like menus, data display and so on).

To achieve that, clipping has to be enabled. In UWP, setting the Clip property does exactly that.

The most common usecase is to clip at the bounds of the canvas. In WPF, there was a special property for that, ClipToBounds. Unfortunately, this property does not exist in UWP. But, happy for us, there are workarounds. I’ll present 2 of them in this article.

Handling Canvas’ Events

Probably the easiest way is to just handle the events Canvas raises and set the Clip property to the current size in the handlers.

In your code behind you would then have something like this:

private static void Clip(FrameworkElement element)
{
    var clip = new RectangleGeometry { Rect = new Rect(0, 0, element.ActualWidth, element.ActualHeight) };
    element.Clip = clip;
    }
}

public CanvasContainingUserControl(){
    canvas.Loaded += (s, e) => Clip(canvas);
    canvas.SizeChanged += (s, e) => Clip(canvas);
}

This works, but is not particular well crafted code. It has to be repeated for every element (UserControl, Page, Window) that contains a Canvas.

A simple fix would be to create a ClippingCanvas, which inherits from Canvas and adds exactly that behavior. A straightforward implementation of it would be this:

public class ClippingCanvas : Canvas
{
    private static void Clip(FrameworkElement element)
    {
        var clip = new RectangleGeometry { Rect = new Rect(0, 0, element.ActualWidth, element.ActualHeight) };
        element.Clip = clip;
        }
    }

    public ClippingCanvas(){
        this.Loaded += (s, e) => Clip(this);
        this.SizeChanged += (s, e) => Clip(this);
    }
}

This class can now be used instead of Canvas to stop rendering outside its dimensions.

But it still is not the best way to achieve this. This code has to be repeated for each class that exhibits the same behavior as canvas. The next section implements that behavior with maximum code reuse using attached properties.

Adding an Attached Property

Attached properties allow implementing a behavior once which can then be reused for a wide variety of elements.

In this case, I created a ClipToBounds class, which exposes exactly one attached property (also called ClipToBounds).

public class ClipToBounds
{
    public static bool GetClipToBounds(DependencyObject obj)
    {
        return (bool)obj.GetValue(ClipToBoundsProperty);
    }

    public static void SetClipToBounds(DependencyObject obj, bool value)
    {
        obj.SetValue(ClipToBoundsProperty, value);
    }

    // Using a DependencyProperty as the backing store for ClipToBounds.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ClipToBoundsProperty =
        DependencyProperty.RegisterAttached("ClipToBounds", typeof(bool), typeof(ClipToBounds), new PropertyMetadata(false, ClipToBoundsChanged));

    private static void ClipToBoundsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        //use FrameworkElement because it is the highest abstraction that contains safe size
        //UIElement does not contain save size data
        var element = d as FrameworkElement;
        if (element != null)
        {
            element.Loaded += (s, evt) => ClipElement(element);
            element.SizeChanged += (s, evt) => ClipElement(element);
        }
    }

    private static void ClipElement(FrameworkElement element)
    {
        if (GetClipToBounds(element))
        {
            var clip = new RectangleGeometry { Rect = new Rect(0, 0, element.ActualWidth, element.ActualHeight) };
            element.Clip = clip;
        }
    }
}

It does essentially the same thing as described in Handling Canvas’ Events, but is implemented in a more reusable way.

When ClipToBounds is set to true, ClipToBoundsChanged is invoked with the element on which the attached property was set as first parameter. DependencyObject has no notion of clipping, but FrameworkElement does, which is a subclass of DependencyObject. Therefore, it first has to be checked if it is a FrameworkElement.

After that, event handlers for Loaded and SizeChanged are added, just like before.

If you have that class included, you can set the attached property (assuming local references the namespace in which the class ClipToBounds is defined):

<Canvas local:ClipToBounds.ClipToBounds="True">
</Canvas>

Aside:

Technically, UIElement, which is the base class for FrameworkElement implements the Clip property. So it would be event better if this class could be used. Unfortunately this is not possible, because there is no save way to get its size. Its RenderSize property sounds like it, but its documentation explains that it should not be used for this purpose:

RenderSize is not the property to use to obtain size information about a UI element for most scenarios, because in the current implementation it doesn’t have a safe technique for knowing when the value is current.