While creating the next control for my WPFSpark project, I had the requirement of a WPF Grid with rounded corners. Since the Grid does not have the CornerRadius property, the other option I had was to encapsulate the Grid within a Border and set the ClipToBounds property of the Border to true. Then I found out that Border (and all decorators) do not perform the clipping even though the ClipToBounds is set to true.
The MSDN Forum provided an excellent solution on how to derive from the Border class and implement the clipping on your own. The ClippingBorder class mentioned in this site has a _clipRect field (of type RectangleGeometry) which is set as the Clip property of the ClippingBorder’s Child. But in this case the corner radius of the RectangleGeometry is same for all corners. What if you have a border with different corner radius for each corner and you want it to act as a clipping border?
I modified the ClippingBorder class to create the ClipBorder class which will take into account the above mentioned issue. Instead of a RectangleGeometry field, the ClipBorder class uses a PathGeometry to define the clipping Geometry. For this purpose I have used the GeometryHelper class (mentioned in my previous post) to obtain the RoundedRectangleGeometry. I have modified the GeometryHelper code further to take into account the BorderThickness of the ClipBorder.
Here is the code for ClipBorder:
namespace WPFSpark
{
/// <summary>
/// Border which allows Clipping to its border.
/// Useful especially when you need to clip to round corners.
/// </summary>
public class ClipBorder : Border
{
protected override void OnRender(DrawingContext dc)
{
OnApplyChildClip();
base.OnRender(dc);
}
public override UIElement Child
{
get
{
return base.Child;
}
set
{
if (this.Child != value)
{
if (this.Child != null)
{
// Restore original clipping of the old child
this.Child.SetValue(UIElement.ClipProperty, oldClip);
}
if (value != null)
{
// Store the current clipping of the new child
oldClip = value.ReadLocalValue(UIElement.ClipProperty);
}
else
{
// If we dont set it to null we could leak a Geometry object
oldClip = null;
}
base.Child = value;
}
}
}
protected virtual void OnApplyChildClip()
{
UIElement child = this.Child;
if (child != null)
{
// Get the geometry of a rounded rectangle border based on the BorderThickness and CornerRadius
clipGeometry = GeometryHelper.GetRoundRectangle(new Rect(Child.RenderSize), this.BorderThickness, this.CornerRadius);
child.Clip = clipGeometry;
}
}
private Geometry clipGeometry = null;
private object oldClip;
}
}
Update: There was a small error in the calculation of the RoundedRectangle Geometry when the BorderThickness was 1 pixel. I have rectified it and updated the code. Thanks to Gene for pointing it out.
Here is the updated code for the modified GeometryHelper:
public static Geometry GetRoundRectangle(Rect baseRect, Thickness thickness, CornerRadius cornerRadius)
{
// Normalizing the corner radius
if (cornerRadius.TopLeft < Double.Epsilon)
cornerRadius.TopLeft = 0.0;
if (cornerRadius.TopRight < Double.Epsilon)
cornerRadius.TopRight = 0.0;
if (cornerRadius.BottomLeft < Double.Epsilon)
cornerRadius.BottomLeft = 0.0;
if (cornerRadius.BottomRight < Double.Epsilon)
cornerRadius.BottomRight = 0.0;
// Taking the border thickness into account
double leftHalf = thickness.Left * 0.5;
if (leftHalf < Double.Epsilon)
leftHalf = 0.0;
double topHalf = thickness.Top * 0.5;
if (topHalf < Double.Epsilon)
topHalf = 0.0;
double rightHalf = thickness.Right * 0.5;
if (rightHalf < Double.Epsilon)
rightHalf = 0.0;
double bottomHalf = thickness.Bottom * 0.5;
if (bottomHalf < Double.Epsilon)
bottomHalf = 0.0;
// Create the rectangles for the corners that needs to be curved in the base rectangle
// TopLeft Rectangle
Rect topLeftRect = new Rect(baseRect.Location.X,
baseRect.Location.Y,
Math.Max(0.0, cornerRadius.TopLeft - leftHalf),
Math.Max(0.0, cornerRadius.TopLeft - rightHalf));
// TopRight Rectangle
Rect topRightRect = new Rect(baseRect.Location.X + baseRect.Width - cornerRadius.TopRight + rightHalf,
baseRect.Location.Y,
Math.Max(0.0, cornerRadius.TopRight - rightHalf),
Math.Max(0.0, cornerRadius.TopRight - topHalf));
// BottomRight Rectangle
Rect bottomRightRect = new Rect(baseRect.Location.X + baseRect.Width - cornerRadius.BottomRight + rightHalf,
baseRect.Location.Y + baseRect.Height - cornerRadius.BottomRight + bottomHalf,
Math.Max(0.0, cornerRadius.BottomRight - rightHalf),
Math.Max(0.0, cornerRadius.BottomRight - bottomHalf));
// BottomLeft Rectangle
Rect bottomLeftRect = new Rect(baseRect.Location.X,
baseRect.Location.Y + baseRect.Height - cornerRadius.BottomLeft + bottomHalf,
Math.Max(0.0, cornerRadius.BottomLeft - leftHalf),
Math.Max(0.0, cornerRadius.BottomLeft - bottomHalf));
// Adjust the width of the TopLeft and TopRight rectangles so that they are proportional to the width of the baseRect
if (topLeftRect.Right > topRightRect.Left)
{
double newWidth = (topLeftRect.Width / (topLeftRect.Width + topRightRect.Width)) * baseRect.Width;
topLeftRect = new Rect(topLeftRect.Location.X, topLeftRect.Location.Y, newWidth, topLeftRect.Height);
topRightRect = new Rect(baseRect.Left + newWidth, topRightRect.Location.Y, Math.Max(0.0, baseRect.Width - newWidth), topRightRect.Height);
}
// Adjust the height of the TopRight and BottomRight rectangles so that they are proportional to the height of the baseRect
if (topRightRect.Bottom > bottomRightRect.Top)
{
double newHeight = (topRightRect.Height / (topRightRect.Height + bottomRightRect.Height)) * baseRect.Height;
topRightRect = new Rect(topRightRect.Location.X, topRightRect.Location.Y, topRightRect.Width, newHeight);
bottomRightRect = new Rect(bottomRightRect.Location.X, baseRect.Top + newHeight, bottomRightRect.Width, Math.Max(0.0, baseRect.Height - newHeight));
}
// Adjust the width of the BottomLeft and BottomRight rectangles so that they are proportional to the width of the baseRect
if (bottomRightRect.Left < bottomLeftRect.Right)
{
double newWidth = (bottomLeftRect.Width / (bottomLeftRect.Width + bottomRightRect.Width)) * baseRect.Width;
bottomLeftRect = new Rect(bottomLeftRect.Location.X, bottomLeftRect.Location.Y, newWidth, bottomLeftRect.Height);
bottomRightRect = new Rect(baseRect.Left + newWidth, bottomRightRect.Location.Y, Math.Max(0.0, baseRect.Width - newWidth), bottomRightRect.Height);
}
// Adjust the height of the TopLeft and BottomLeft rectangles so that they are proportional to the height of the baseRect
if (bottomLeftRect.Top < topLeftRect.Bottom)
{
double newHeight = (topLeftRect.Height / (topLeftRect.Height + bottomLeftRect.Height)) * baseRect.Height;
topLeftRect = new Rect(topLeftRect.Location.X, topLeftRect.Location.Y, topLeftRect.Width, newHeight);
bottomLeftRect = new Rect(bottomLeftRect.Location.X, baseRect.Top + newHeight, bottomLeftRect.Width, Math.Max(0.0, baseRect.Height - newHeight));
}
StreamGeometry roundedRectGeometry = new StreamGeometry();
using (StreamGeometryContext context = roundedRectGeometry.Open())
{
// Begin from the Bottom of the TopLeft Arc and proceed clockwise
context.BeginFigure(topLeftRect.BottomLeft, true, true);
// TopLeft Arc
context.ArcTo(topLeftRect.TopRight, topLeftRect.Size, 0, false, SweepDirection.Clockwise, true, true);
// Top Line
context.LineTo(topRightRect.TopLeft, true, true);
// TopRight Arc
context.ArcTo(topRightRect.BottomRight, topRightRect.Size, 0, false, SweepDirection.Clockwise, true, true);
// Right Line
context.LineTo(bottomRightRect.TopRight, true, true);
// BottomRight Arc
context.ArcTo(bottomRightRect.BottomLeft, bottomRightRect.Size, 0, false, SweepDirection.Clockwise, true, true);
// Bottom Line
context.LineTo(bottomLeftRect.BottomRight, true, true);
// BottomLeft Arc
context.ArcTo(bottomLeftRect.TopLeft, bottomLeftRect.Size, 0, false, SweepDirection.Clockwise, true, true);
}
return roundedRectGeometry;
}
Recent Comments