How to make the contents of a round-cornered border be also round-cornered?

Wpf

Wpf Problem Overview


I have a border element with rounded corners containing a 3x3 grid. The corners of the grid are sticking out of the border. How can I fix that? I tried using ClipToBounds but didn't get anywhere. Thanks for your help

Wpf Solutions


Solution 1 - Wpf

Here are the highlights of this thread mentioned by Jobi

  • None of the decorators (i.e. Border) or layout panels (i.e. Stackpanel) come with this behavior out-of-the-box.
  • ClipToBounds is for layout. ClipToBounds does not prevent an element from drawing outside its bounds; it just prevents children's layouts from 'spilling'. Additionally ClipToBounds=True is not needed for most elements because their implementations dont allow their content's layout to spill anyway. The most notable exception is Canvas.
  • Finally Border considers the rounded corners to be drawings inside the bounds of its layout.

Here is an implementation of a class that inherits from Border and implements the proper functionality:

     /// <Remarks>
    ///     As a side effect ClippingBorder will surpress any databinding or animation of 
    ///         its childs UIElement.Clip property until the child is removed from ClippingBorder
    /// </Remarks>
    public class ClippingBorder : 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
                        this.Child.SetValue(UIElement.ClipProperty, _oldClip);
                    }
                    
                    if(value != null)
                    {
                        _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)
            {
                _clipRect.RadiusX = _clipRect.RadiusY = Math.Max(0.0, this.CornerRadius.TopLeft - (this.BorderThickness.Left * 0.5));
                _clipRect.Rect = new Rect(Child.RenderSize);
                child.Clip = _clipRect;
            }
        }
        
        private RectangleGeometry _clipRect = new RectangleGeometry();
        private object _oldClip;
    }

Solution 2 - Wpf

Pure XAML:

<Border CornerRadius="30" Background="Green">
    <Border.OpacityMask>
        <VisualBrush>
            <VisualBrush.Visual>
                <Border 
                    Background="Black"
                    SnapsToDevicePixels="True"
                    CornerRadius="{Binding CornerRadius, RelativeSource={RelativeSource AncestorType=Border}}"
                    Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Border}}"
                    Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Border}}"
                    />
            </VisualBrush.Visual>
        </VisualBrush>
    </Border.OpacityMask>
    <TextBlock Text="asdas das d asd a sd a sda" />
</Border>

Update: Found a better way to achieve the same result. You can also replace Border with any other element now.

<Grid>
    <Grid.OpacityMask>
        <VisualBrush Visual="{Binding ElementName=Border1}" />
    </Grid.OpacityMask>
    <Border x:Name="Border1" CornerRadius="30" Background="Green" />
    <TextBlock Text="asdas das d asd a sd a sda" />
</Grid>

[![Example][1]][1] [1]: http://i.stack.imgur.com/xXUyh.png

Solution 3 - Wpf

As Micah mentioned ClipToBounds will not work with Border.ConerRadius.

There is UIElement.Clip property, which Border inheritances.

If you know the exact size of the border, then here is the solution:

<Border Background="Blue" CornerRadius="3" Height="100" Width="100">
      <Border.Clip>
        <RectangleGeometry RadiusX="3" RadiusY="3" Rect="0,0,100,100"/>
      </Border.Clip>
      <Grid Background="Green"/>
</Border>

If the size is not known or dynamic then Converter for Border.Clip can be used. See the solution here.

Solution 4 - Wpf

So I just came across this solution, then followed into msdn forum link that Jobi provided and spent 20 minutes writing my own ClippingBorder control.

Then I realized that CornerRadius property type is not a double, but System.Windows.CornerRaduis which accepts 4 doubles, one for each corner.

So I'm going to list another alternative solution now, which will most likely satisfy the requirements of most people who will stumble upon this post in the future...

Let's say you have XAML which looks like this:

<Border CornerRadius="10">
    <Grid>
        ... your UI ...
    </Grid>
</Border>

And the problem is that the background for the Grid element bleeds through and shows past the rounded corners. Make sure your <Grid> has transparent background instead of assigning the same brush to "Background" property of the <Border> element. No more bleeding past the corners and no need for a whole bunch of CustomControl code.

It's true that in theory, client area still have the potential of drawing past the edge of the corner, but you control that content so you as developer should be able to either have enough padding, or make sure the shape of the control next to the edge is appropriate (in my case, my buttons are round, so fit very nicely in the corner without any problems).

Solution 5 - Wpf

Using @Andrew Mikhailov's solution, you can define a simple class, which makes defining a VisualBrush for each affected element manually unnecessary:

public class ClippedBorder : Border
{
    public ClippedBorder() : base()
    {
        var e = new Border()
        {
            Background = Brushes.Black,
            SnapsToDevicePixels = true,
        };
        e.SetBinding(Border.CornerRadiusProperty, new Binding()
        {
            Mode = BindingMode.OneWay,
            Path = new PropertyPath("CornerRadius"),
            Source = this
        });
        e.SetBinding(Border.HeightProperty, new Binding()
        {
            Mode = BindingMode.OneWay,
            Path = new PropertyPath("ActualHeight"),
            Source = this
        });
        e.SetBinding(Border.WidthProperty, new Binding()
        {
            Mode = BindingMode.OneWay,
            Path = new PropertyPath("ActualWidth"),
            Source = this
        });

        OpacityMask = new VisualBrush(e);
    }
}

To test this, just compile the following two samples:

<!-- You should see a blue rectangle with rounded corners/no red! -->
<Controls:ClippedBorder
    Background="Red"
    CornerRadius="10"
    Height="425"
    HorizontalAlignment="Center"
    VerticalAlignment="Center"
    Width="425">
    <Border Background="Blue">
    </Border>
</Controls:ClippedBorder>

<!-- You should see a blue rectangle with NO rounded corners/still no red! -->
<Border
    Background="Red"
    CornerRadius="10"
    Height="425"
    HorizontalAlignment="Center"
    VerticalAlignment="Center"
    Width="425">
    <Border Background="Blue">
    </Border>
</Border>

Solution 6 - Wpf

Make the grid smaller or the border larger. So that the border element completely contains the grid.

Alternatively see if you can make the grid's background transparent, so that the "sticking out" isn't noticeable.

Update: Oops, didn't notice this was a WPF question. I'm not familiar with that. This was general HTML/CSS advice. Maybe it helps...

Solution 7 - Wpf

I don't like to use a custom control. Created a behavior instead.

using System.Linq;
using System.Windows;
using System.Windows.Interactivity;

/// <summary>
/// Base class for behaviors that could be used in style.
/// </summary>
/// <typeparam name="TComponent">Component type.</typeparam>
/// <typeparam name="TBehavior">Behavior type.</typeparam>
public class AttachableForStyleBehavior<TComponent, TBehavior> : Behavior<TComponent>
        where TComponent : System.Windows.DependencyObject
        where TBehavior : AttachableForStyleBehavior<TComponent, TBehavior>, new()
{
#pragma warning disable SA1401 // Field must be private.

    /// <summary>
    /// IsEnabledForStyle attached property.
    /// </summary>
    public static DependencyProperty IsEnabledForStyleProperty =
        DependencyProperty.RegisterAttached("IsEnabledForStyle", typeof(bool),
        typeof(AttachableForStyleBehavior<TComponent, TBehavior>), new FrameworkPropertyMetadata(false, OnIsEnabledForStyleChanged));

#pragma warning restore SA1401

    /// <summary>
    /// Sets IsEnabledForStyle value for element.
    /// </summary>
    public static void SetIsEnabledForStyle(UIElement element, bool value)
    {
        element.SetValue(IsEnabledForStyleProperty, value);
    }

    /// <summary>
    /// Gets IsEnabledForStyle value for element.
    /// </summary>
    public static bool GetIsEnabledForStyle(UIElement element)
    {
        return (bool)element.GetValue(IsEnabledForStyleProperty);
    }

    private static void OnIsEnabledForStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        UIElement uie = d as UIElement;

        if (uie != null)
        {
            var behColl = Interaction.GetBehaviors(uie);
            var existingBehavior = behColl.FirstOrDefault(b => b.GetType() ==
                  typeof(TBehavior)) as TBehavior;

            if ((bool)e.NewValue == false && existingBehavior != null)
            {
                behColl.Remove(existingBehavior);
            }
            else if ((bool)e.NewValue == true && existingBehavior == null)
            {
                behColl.Add(new TBehavior());
            }
        }
    }
}

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

/// <summary>
/// Behavior that creates opacity mask brush.
/// </summary>
internal class OpacityMaskBehavior : AttachableForStyleBehavior<Border, OpacityMaskBehavior>
{
    protected override void OnAttached()
    {
        base.OnAttached();

        var border = new Border()
        {
            Background = Brushes.Black,
            SnapsToDevicePixels = true,
        };

        border.SetBinding(Border.CornerRadiusProperty, new Binding()
        {
            Mode = BindingMode.OneWay,
            Path = new PropertyPath("CornerRadius"),
            Source = AssociatedObject
        });

        border.SetBinding(FrameworkElement.HeightProperty, new Binding()
        {
            Mode = BindingMode.OneWay,
            Path = new PropertyPath("ActualHeight"),
            Source = AssociatedObject
        });

        border.SetBinding(FrameworkElement.WidthProperty, new Binding()
        {
            Mode = BindingMode.OneWay,
            Path = new PropertyPath("ActualWidth"),
            Source = AssociatedObject
        });

        AssociatedObject.OpacityMask = new VisualBrush(border);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.OpacityMask = null;
    }
}

<Style x:Key="BorderWithRoundCornersStyle" TargetType="{x:Type Border}">
    <Setter Property="CornerRadius" Value="50" />
    <Setter Property="behaviors:OpacityMaskBehavior.IsEnabledForStyle" Value="True" />
</Style>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGus CavalcantiView Question on Stackoverflow
Solution 1 - WpfMicahView Answer on Stackoverflow
Solution 2 - WpfAndrew MikhailovView Answer on Stackoverflow
Solution 3 - WpfArtur AView Answer on Stackoverflow
Solution 4 - WpfDXMView Answer on Stackoverflow
Solution 5 - Wpfuser1618054View Answer on Stackoverflow
Solution 6 - WpfVilx-View Answer on Stackoverflow
Solution 7 - WpfDer_MeisterView Answer on Stackoverflow