With Silverlight, Panels do not clip their contents by default. See the following example:
Where we have a Grid containing another Grid which itself contains an ellipse, and a Canvas which contains an ellipse:
<Grid x:Name="LayoutRoot" Background="White"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid Grid.Column="0" Background="Blue" Margin="20"> <Grid Background="Yellow" Margin="20,40,-20,20"> <Ellipse Fill="LightGreen" Width="80" Height="80" Margin="-40, -40, 0, 0"/> </Grid> </Grid> <Canvas Grid.Column="1" Background="Aqua" Margin="20" > <Ellipse Fill="Red" Canvas.Top="-10" Canvas.Left="-10" Width="130" Height="130"/> </Canvas> </Grid> |
Often this is not the desired effect (although it is actually quite a useful feature of Canvas; You can simply add a Canvas to your visual tree without explicitly or implicitly setting its Size and use it as a mechanism for absolute positioning its children).
Fortunately Silverlight provides a Clip property on UIElement, allowing you to provide the clipping geometry for the element:
<Grid Width="200" Height="100"> <Grid.Clip> <RectangleGeometry Rect="0, 0, 200, 100"/> </Grid.Clip> </Grid> |
The above example creates a clipping geometry which matches the rectangular geometry of the Grid itself. Clearly more funky clipping geometries can be created, allowing for really cool effects, however most of the time I simply want my Panel clipped so that its children cannot escape!
The above example has a few problems, Firstly, it is a bit long-winded having to explicitly create the geometry each time I want to clip; Secondly, if my Grid’s size is calculated from its parent’s layout, how can I define the clip geometry in my XAML?; Finally, if my Grid’s geometry changes, its clipped geometry does not change.
In order to solve this problem I created a simple little attached behaviour, which allows you to define the clipping using the attached property Clip.ToBounds as illustrated below:
<UserControl x:Class="SilverlightClipToBounds.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:util="clr-namespace:Util" Width="300" Height="200"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid Grid.Column="0" Background="Blue" Margin="20" util:Clip.ToBounds="true"> <Grid Background="Yellow" Margin="20,40,-20,20" util:Clip.ToBounds="true"> <Ellipse Fill="LightGreen" Width="80" Height="80" Margin="-40, -40, 0, 0"/> </Grid> </Grid> <Canvas Grid.Column="1" Background="Aqua" Margin="20" util:Clip.ToBounds="true"> <Ellipse Fill="Red" Canvas.Top="-10" Canvas.Left="-10" Width="130" Height="130"/> </Canvas> </Grid> </UserControl> |
The result can be seen below:
And here is the code for the attached behaviour itself:
public class Clip { public static bool GetToBounds(DependencyObject depObj) { return (bool)depObj.GetValue(ToBoundsProperty); } public static void SetToBounds(DependencyObject depObj, bool clipToBounds) { depObj.SetValue(ToBoundsProperty, clipToBounds); } /// <summary> /// Identifies the ToBounds Dependency Property. /// <summary> public static readonly DependencyProperty ToBoundsProperty = DependencyProperty.RegisterAttached("ToBounds", typeof(bool), typeof(Clip), new PropertyMetadata(false, OnToBoundsPropertyChanged)); private static void OnToBoundsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { FrameworkElement fe = d as FrameworkElement; if (fe != null) { ClipToBounds(fe); // whenever the element which this property is attached to is loaded // or re-sizes, we need to update its clipping geometry fe.Loaded += new RoutedEventHandler(fe_Loaded); fe.SizeChanged += new SizeChangedEventHandler(fe_SizeChanged); } } /// <summary> /// Creates a rectangular clipping geometry which matches the geometry of the /// passed element /// </summary> private static void ClipToBounds(FrameworkElement fe) { if (GetToBounds(fe)) { fe.Clip = new RectangleGeometry() { Rect = new Rect(0, 0, fe.ActualWidth, fe.ActualHeight) }; } else { fe.Clip = null; } } static void fe_SizeChanged(object sender, SizeChangedEventArgs e) { ClipToBounds(sender as FrameworkElement); } static void fe_Loaded(object sender, RoutedEventArgs e) { ClipToBounds(sender as FrameworkElement); } } |
When the ToBounds property is associated with an element, ClipToBounds is invoked to create a rectangular clip geometry. We also add event handlers for Loaded, which is a very useful event which is fired when an element has been laid out and rendered, in other words it’s size will have been computed, and SizeChanged. In the event handlers for both we simply update the clipping geometry.
This can be seen in action here, where clicking on the Grids or Canvas increases their size, with the clipping geometry growing accordingly:
You can download a demo project here: sliverlightcliptobounds.zip
Can I Clip It?, Yes You Can!
Regards, Colin E.
Tags: attached behaviour, silverlight


email: ceberhardt@scottlogic.co.uk
on Google+





Thanks a lot! Also works on the WP7
[...] The images were rendered OUT of BOUNDS of the associated object. Which was fixed using the attached property from http://www.scottlogic.co.uk/blog/colin/2009/05/silverlight-cliptobounds-can-i-clip-it-yes-you-can/ [...]
[...] The images were rendered OUT of BOUNDS of the associated object. Which was fixed using the attached property from http://www.scottlogic.co.uk/blog/colin/2009/05/silverlight-cliptobounds-can-i-clip-it-yes-you-can/ [...]
[...] The images were rendered OUT of BOUNDS of the associated object. Which was fixed using the attached property from http://www.scottlogic.co.uk/blog/colin/2009/05/silverlight-cliptobounds-can-i-clip-it-yes-you-can/ [...]
Very useful.
Thanks a lot!
Short, practical, straight-to-the-point solution for something that seems like MS have forgot to add.
It was very helpful for me! Thanks a lot!
WOW, this is awesome
thanks!
You can also do this:
Canvas my_canvas = new Canvas();
my_canvas.SizeChanged += (o, e) =>
{
my_canvas.Clip = null;
RectangleGeometry rg = new RectangleGeometry();
rg.Rect = new Rect(0, 0, my_canvas.ActualWidth, my_canvas.ActualHeight);
my_canvas.Clip = rg;
};
Hi … this is basically what I am doing, but wrapping up the code into an attached behaviour to make it re-usable and XAML-freindly.
Colin E.
[...] http://www.scottlogic.co.uk/blog/colin/2009/05/silverlight-cliptobounds-can-i-clip-it-yes-you-can/ [...]
Nice work! Just what I needed!
Hey Colin,
I am wondering what the policy is regarding using your Clip class within a commercial project? It was exactly what I needed to get my project working as I wanted.
Currently I have the class included in my project and I added the URL to your blog (this page) in the comments section.
Thanks,
Ian
PS: I would also like to acknowledge the awesomeness of including Tribe lyrics. Very nice.
Hi Ian,
I am happy for you to make commercial use of this class, as long as you link to this blog in some way.
Glad you liked the subtle reference
Colin E.
Thanks.
That helped a lot.
Thank you! I took your basic code and adjusted it so it isn’t just a “clip to bounds” but a “clip to relative bounds”. Instead of a boolean, you can set a Rect, but you set it in terms of relative positions, so 0,0,1,1 is the full bounds. 0,0.5,0.5,0.5 would show just the lower left quadrant.
Since I got the basic code from you, I figure it’s only fair to share my modification back with you and the rest of the community.
///
/// This class provides a way for controls to set a rectangular clip region that scales with them. This class
/// gives an easy way to specify relative clip geometry so you can have a control that renders outside of its
/// bounds still be clipped down to those bounds without having to specify a fixed width and height.
///
///
/// To use this class, set the RelativeRectangleClip.ToRect property on a FrameworkElement. The rectangle
/// should use relative positions and relative height/width.
/// A value of “0,0,1,1″ will set a clip rectangle of the full actual bounds of the FrameworkElement.
/// Setting this property overrides any assignment of the element’s own Clip property.
///
public class RelativeRectangleClip
{
///
/// Gets the rectangle that will be used to create the clip geometry for the given object.
///
/// The object
/// The current relative clip rectangle
public static Rect GetToRect(DependencyObject depObj)
{
return (Rect)depObj.GetValue(ToRectProperty);
}
///
/// Sets the rectangle that will be used to create the clip geometry for the given object. Positions
/// and sizes should be relative values. Actual values will be calculated when the object is measured
/// and arranged.
///
/// The object
/// The desired relative clip rectangle
public static void SetToRect(DependencyObject depObj, Rect clipToRect)
{
depObj.SetValue(ToRectProperty, clipToRect);
}
///
/// Identifies the ToRect Dependency Property.
///
public static readonly DependencyProperty ToRectProperty =
DependencyProperty.RegisterAttached(“ToRect”, typeof(Rect),
typeof(RelativeRectangleClip), new PropertyMetadata(OnToRectPropertyChanged));
private static void OnToRectPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement fe = d as FrameworkElement;
if (fe != null)
{
ClipToRect(fe);
// whenever the element which this property is attached to is loaded
// or re-sizes, we need to update its clipping geometry
fe.Loaded += new RoutedEventHandler(fe_Loaded);
fe.SizeChanged += new SizeChangedEventHandler(fe_SizeChanged);
}
}
///
/// Creates a rectangular clipping geometry which matches the geometry of the
/// passed element, using relative positioning of the rect based on the ToRect property.
///
private static void ClipToRect(FrameworkElement fe)
{
Rect clip = GetToRect(fe);
if ((clip.Height != 0) || (clip.Width != 0))
{
fe.Clip = new RectangleGeometry()
{
Rect = new Rect(fe.ActualWidth * clip.Left, fe.ActualHeight * clip.Top, fe.ActualWidth * clip.Width, fe.ActualHeight * clip.Height)
};
}
else
{
fe.Clip = null;
}
}
static void fe_SizeChanged(object sender, SizeChangedEventArgs e)
{
ClipToRect(sender as FrameworkElement);
}
static void fe_Loaded(object sender, RoutedEventArgs e)
{
ClipToRect(sender as FrameworkElement);
}
}
no one’s commented on your tribe called quest reference! well done.
Finally …. thank you
Regards, Colin E.
Great idea. However I would like this behavior even more if it was actually a behavior. Then it is just a matter of dragging it onto the controls inside Blend.
Wouldn’t it also make more sense to set this behavior on the parent and it would clip its children, instead of having to set it on each of the children? (I would think that’s the most common usecase that you want all the children to be clipped)
Making this a behaviour sounds like a very good idea, perhaps this would be a good excuse for me to learn about behaviours!
No- I do not think this property should cascade to the elements children. Very often it is just a single UI element which you want to use for clipping.
Regards, Colin E.
Well Done! We were just talking about this…clipping.. Thanks!
p.s. I just wanted to add that I tried the StackPanels both with and without using your Clip class, and got the same result — they were not clipped to what I expected to be the clipping region. If you do get a chance to take a look at it, could you shoot me an e-mail with any thoughts that you have on what I am doing wrong?
Thx
Helen
HI Colin,
I created a little example of image transitions using your Clip class. The full source code can be downloaded from http://www.agile-soft.com/Silverlight.aspx — you can see the sample embedded in the .aspx page and there is a link at the bottom of the page to download the source.
If you look at the MainPage.xaml, you will see 2 StackPanels with radio buttons, which I slide in and out in response to a button click. To get the clipping to work, I put each of them in a Border Control. Simply remove the Border Control and you will see the problem I am having.
I’d love to learn more about it.
Thanks
Helen
Colin, you probably realize this already, but your Clip class does not seem to work for container classes. I tried to use it to clip a StackPanel, but until I made my StackPanel the child of a Border Control, I could not get the expected behaviour.
Cheers,
Helen
Hi Helen,
I am not sure what you mean, the StackPanel clips its contents without the need for my ‘Clip’ class. See the following example:
<UserControl x:Class="SilverlightClipToBounds.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="100" Height="200">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Grid.Column="1" Background="Aqua" Margin="20" >
<Ellipse Fill="Red" Canvas.Top="-30" Canvas.Left="-10" Width="130" Height="130"/>
</StackPanel>
</Grid>
</UserControl>
Can you please give an example of some XAML which is not working for you?
Colin E.
This brings me to an idea:…
Colin, your Clip class really helped a sample project I created to show some animations for image transitions. All the animations are created programmatically. You can see it running at http://www.agile-soft.com/Silverlight.aspx, and you can download the source as well!
Awesome! Nice work, and thanks for posting the code, it compiled and worked without a single change.
Once again attached behaviors save the day! This is the third time I’ve run into a problem with Silverlight, found myself bewildered that SL doesn’t do what I want out-of-the-box, and then either find or write an Attached Behavior to do exactly it.
Thanks!
Thanks – glad you liked it
Excellent example of utilizing attached behaviors
Thanks Mike, glad you liked it
What I like about this attached behaviour is that it is so simple, yet very useful.
Regards, Colin E.
Silverlight ClipToBounds – Can I Clip It?, Yes You Can! – Colin Eberhardt…
Thank you for submitting this cool story – Trackback from DotNetShoutout…