ClipToBounds – AttachedProperty

This is a very useful Attached Property that I created to clip the content of a panel. If you understand the problem and all you want is the Attached Property code, go to the bottom of the post and copy the behavior class. If you want to understand more, keep reading.

So here’s the problem, Let’s say I have a Grid. Inside this Grid I have an element, a rectangle.

<Grid Width="500" Height="500" Background="DarkRed">    <Rectangle Fill="DarkBlue" Width="100" Height="100" HorizontalAlignment="Left" VerticalAlignment="Top" /></Grid>

With that xaml code I will get this visual:

image

Now lets say I want to move the blue rectangle around by changing the TranslateX and TranslateY values. In my case I want to move the rectangle to the top left corner, about 50 pixels. So here’s the xaml code to do that:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">        <Grid Width="500" Height="500" Background="DarkRed">            <Rectangle Fill="DarkBlue" Width="100" Height="100" HorizontalAlignment="Left" VerticalAlignment="Top" RenderTransformOrigin="0.5,0.5" >                <Rectangle.RenderTransform>                    <CompositeTransform TranslateX="-50" TranslateY="-50"/>                </Rectangle.RenderTransform>            </Rectangle>        </Grid>    </Grid>

You might expect that the rectangle will be trimmed inside the Grid. But this is what we get:

image

The rectangle floats over and renders outside the container Grid. There’s a quick fix to that is to set the Clip property of that Grid, and pass a rectangle of boundaries to it. This is how we fix it:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">        <Grid Width="500" Height="500" Background="DarkRed">            <Grid.Clip>                <RectangleGeometry Rect="0,0,500,500" />            </Grid.Clip>            <Rectangle Fill="DarkBlue" Width="100" Height="100" HorizontalAlignment="Left" VerticalAlignment="Top" RenderTransformOrigin="0.5,0.5" >                <Rectangle.RenderTransform>                    <CompositeTransform TranslateX="-50" TranslateY="-50"/>                </Rectangle.RenderTransform>            </Rectangle>        </Grid>    </Grid>

And this is the output:

image

So that fixed our problem, now the rectangle is getting clipped inside our container (the Grid).

But this is not an ideal solution. First notice how we had to pass the size of the Grid (Rect=”0,0,500,500”). What will happen if the Grid size is unknown? (for example if its being controlled by a Grid ColumnDefinition). Second, what will happen if the size of the Grid changed at runtime? (maybe going to a portrait mode).

One way to solve this is to write the logic in one place and attach it to the Grid. To do this we will create an Attached Property which will take care of the clipping.

First, create a new class and name it “ClipToBounds”, and inherit a DependencyObject:

public class ClipToBounds : DependencyObject{}

Then we will need to add a property, which will enable/disable the clipping. This is our property:

public class ClipToBounds : DependencyObject{    public static bool GetIsEnabled(DependencyObject obj)    {        return (bool)obj.GetValue(IsEnabledProperty);    }

    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }

    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ClipToBounds2), new PropertyMetadata(false,OnIsEnabledChanged));

    private static void OnIsEnabledChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {

    }
}

It seems like we need to do a lot of typing there to implement this property. But actually No! Visual studio comes with very handy snippets. In this case we will use “propa” snippet to add the attached property. Type propa, and press Tab twice.

image

So in the previous code that we wrote, we registered this attached property called IsEnabled, Notice the last part of the registration line. We added an event hander, which will be called everytime the property value changed. We will use this everytime the value change, to register/unregister the events.

What we need to do now is to add the RectangleGeometry object to the Clip property of the Grid. We will need to do that in two scenarios, once the Grid is shown the first time (event loaded) and everytime the size of the Grid changes (event size changed).

So this is the complete implementation of the Attached Property:

public class ClipToBounds : DependencyObject{    public static bool GetIsEnabled(DependencyObject obj)    {        return (bool)obj.GetValue(IsEnabledProperty);    }

    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }
    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ClipToBounds2), new PropertyMetadata(false,OnIsEnabledChanged));

    private static void OnIsEnabledChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var panel = d as Panel;
        if (panel != null)
        {
            if ((bool)e.NewValue)
            {
                panel.Loaded += panelLoaded;
                panel.SizeChanged += panel
SizeChanged;
            }
            else
            {
                panel.Loaded -= panelLoaded;
                panel.SizeChanged -= panel
SizeChanged;
            }
        }
    }

    static void panel_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        addClip((sender as Panel));
    }

    static void panel_Loaded(object sender, RoutedEventArgs e)
    {
        addClip((sender as Panel));
    }
    private static void addClip(Panel panel)
    {
        var rect = new Rect(new Point(0, 0), panel.RenderSize);
        panel.Clip = new Windows.UI.Xaml.Media.RectangleGeometry() { Rect = rect };
    }
}

And finally we use this attached property in our container:

<Grid>        <Grid Width="500" Height="500" Background="DarkRed" local:ClipToBounds.IsEnabled="True">            <Rectangle Fill="DarkBlue" Width="100" Height="100" HorizontalAlignment="Left" VerticalAlignment="Top" RenderTransformOrigin="0.5,0.5" >                <Rectangle.RenderTransform>                    <CompositeTransform TranslateX="-50" TranslateY="-50"/>                </Rectangle.RenderTransform>            </Rectangle>        </Grid>    </Grid>

Tareq Ateik

Read more posts by this author.