WPF – Easy rounded corners for anything

UPDATES:
> You can see another simple example here.
> Why stop at simple geometry masks?  Howabout images with holes? 🙂
> Why stop at WPF?  Here’s a Silverlight 3 version!

WPF’s Border element allows you to specify a different radius for each corner.  Unfortunately it doesn’t clip content to fit inside the boundary (example here and below):

image

While there are other solutions, this one is my favorite so far:

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Background="Black">

    <!-- Rounded yellow border -->
    <Border BorderThickness="3" BorderBrush="Yellow" CornerRadius="10" Padding="2"
        HorizontalAlignment="Center" VerticalAlignment="Center">

      <Grid>

         <!-- Rounded mask (stretches to fill Grid) -->
         <Border Name="mask" Background="White" CornerRadius="7"/>

         <!-- Main content container -->
         <StackPanel>

             <!-- Use a VisualBrush of 'mask' as the opacity mask -->
             <StackPanel.OpacityMask>
                 <VisualBrush Visual="{Binding ElementName=mask}"/>
             </StackPanel.OpacityMask>

             <!-- Any content -->
             <Image Source="https://chriscavanagh.files.wordpress.com/2006/12/chriss-blog-banner.jpg"/>
             <Rectangle Height="50" Fill="Red"/>
             <Rectangle Height="50" Fill="White"/>
             <Rectangle Height="50" Fill="Blue"/>

         </StackPanel>

      </Grid>

    </Border>

</Page>

All it does is include a ‘mask’ Border element as a sibling of the content you want to clip.  In the content it uses a VisualBrush bound to that mask.  The mask will be automatically sized to your content, so it’s a nice "set and forget" solution 🙂

The downside is it won’t work in Silverlight because ElementName binding isn’t supported (yet). No doubt it’s possible to assign the VisualBrush in code-behind though! (let me know if you try it). (oops, neither is VisualBrush!).  There’s now a Silverlight version here!

Hope this helps (and please let me know of any better / cleaner solutions).

kick it

74 Comments

  1. Hi Chris,

    Can you try something like this, at your leisure, with a file loaded as a resource, and let me know if you get strange results when resizing the window? The rounded corners seem to collapse. Here, I really just plugged in your XAML to the stock WPF project window.

    Reply

  2. Daniel – The problem is caused by the image being stretched bigger than the available area. Because it’s contained in a StackPanel (with default vertical orientation) it’ll allow the content to grow as tall as it wants. To confirm this try changing the StackPanel to a Grid…

    Are you wanting the brushed aluminum image to act as a background? If so, you might prefer to assign it as an ImageBrush to the StackPanel’s Background property (or do your own LinearGradientBrush with an aluminum OpacityMask…).

    Hope this helps!

    Reply

  3. This is not exactly related to mask graphics issue here but I attempted to use this technique with WebBrowser control that is overlapping objects that are below the web control.

    The web control is inside ScrollerViewe inside one of expanders. This is a set Grid and with bottom grid on top this so it at bottom of screen.

    Reply

  4. Stewart – I might be misunderstanding your comment, but WebBrowser isn’t a “proper” WPF control. It’s just a wrapper around the IE ActiveX control and suffers from several restrictions associated with ActiveX. The main problem is it can only render directly to the screen rather than arbitrary surfaces, so WPF can only control its size and position (and can’t do nice things like clip its corners or apply transformations).

    Does that help?

    Reply

  5. Why not just add a padding attribute to the outer border control? This seems overly complex. Am I missing something here???

    Reply

  6. James – The main goal of this is to allow any content to be clipped to a rounded border (or any shape actually), without having to know its size. While it’s easy to define a clip rectangle with rounded corners, you need to know the exact size… Padding only helps if your content doesn’t need to go right up against the border.

    I’ve probably not explained it very well… Try the example above in Kaxaml or IE and resize the window. Then try doing the same thing using other methods. Definitely let me know if you find a simpler / better solution though; I certainly don’t claim to be an expert!

    Thanks for the feedback!

    Reply

  7. One issue with this, I have an image with alpha transparency and don’t want the white background. Yet still need to have the image clipped.

    Reply

  8. Ref My post above, If I place the opacity mask on ANY placeholder then the rest of the content that it contains is not shown.

    Reply

  9. H David – Please send me some XAML that shows the problem. Some elements resize in interesting ways that can affect this (but I’ve not found any “impossible” cases… yet 🙂 )

    Reply

    1. I am having trouble with this leaving me with just the boarder visible with the entire contents of my grid / stack panel masked out. Any idea what I may be doing wrong for this to be happening?

      Reply

  10. Chris,

    I’ve noticed that if you place any hardware accelerated effects onto items contained within the masked element that the masking breaks down. Don’t know if you’ve had this experience or know a work around but I thought it was worth mentioning.

    Reply

  11. Chris,

    I figured it out, the item I was containing was a png image that had alot of transparency around the actual visible image. The overall image ran to the edge of the parent container with rounded corners. When the DropShadow is added the corners break. I placed a large margin around the image and the corners were sorted out.

    Reply

  12. Thanks,

    However, I am using expression blend for silverlight 3 and I am getting a message that “VisualBrush does not exist”

    Am I missing something?

    Please let me know.

    Thanks again.

    Reply

  13. Any ideas how I could get a transparent background with this? Since the Border is used as an Opacity Mask, any tries to set transparency to it also sets transparency to the content.

    Any ideas? 😦

    Reply

  14. PienaZipa – You should be able to do this with extra sibling elements to the mask; something like this:

    http://www.chriscavanagh.com/chris/CornerClippingTransparency.xaml

    (Try “download as” to get the xaml source). This example applies the OpacityMask to both siblings [of the mask], but you really only need to apply it to elements that need to be clipped (so for instance if a sibling had Margin=”20″ or something, it might not need clipping at all).

    Hope tihs helps!

    Reply

    1. Thanks for replying. However, I tried your sample and, no matter how hard I try, it doesn’t work as desired (entirely transparent through to desktop – the window is semi-transparent).

      I posted the relevant XAML for the ListBox at http://pastebin.com/m38a785c2 – could you please take a look at it? Thank you 🙂

      Reply

    1. Thanks 🙂

      Okay, first of all I have a window, which has the Aero-glass transparency effect – you can see the desktop. Then I have this semi-transparent listbox, for which I have created a custom style/controltemplate (it’s the code in the link I posted). This listbox, in turn, generates items which also have semi-transparent backgrounds.

      In the end of the day a have a listbox with a bunch of items, but I can still see the desktop background through them (http://i48.tinypic.com/wa3td3.jpg),
      and I want to make the stackpanel or scrollviewer (the one that contains the generated items, it’s in the code I put in the pastebin) have rounded corners 🙂 However, if I use the mask as it is, I lose the transparency.

      Also for me setting an opacity for the listbox or scrollviewer or stackpanel isn’t going to work since it applies to children (including the generated text, as in the image) 😦

      Reply

  15. Hello.

    This seems a nice, quick way to get the job done. Definitely appreciate the info as we’re all encountering this problem when we really shouldn’t. WPF is all about a new presentation platform geared toward robust visuals (apart from the other important gui-seperation from code aspects) so for it NOT to provide a more streamlined, native approach at natural container clipping for complex custom borders is .. I’ll say annoying.

    Anyway good stuff. I may have missed it but I didn’t see any addition by anyone to address linking the mask corner radius to the main form border radius so I added this to your solution in my code and it seems to work pretty well.

    Assuming the name of the outer form border is borderMainWindow, the following lets the mask corner radius be flexible and not have to be touched again if you change the outer radius settings:

    Thanks again for the good idea to get around this annoying issue.

    -Michael

    Reply

  16. Guess I needed to wrap the XAML oops. I’ll try again. Probably be another missing chunk sorry if so.

    Just in case wrapping in a code tag still didn’t work and my example doesn’t show up, all I’m doing is binding the CornerRadius of the mask to ElementName borderMainWindow, Path CornerRadius.

    Sorry to clutter up the comments with failed attempts at posting my sample. Obliterate and rewrite as you see fit.

    Thanks again,
    -Michael

    Reply

  17. Freakin Sweet! Thanks so much man. I have been looking for an easier way to accomplish this for some time. This is a great idea. Keep up the good work. 🙂

    Reply

  18. Hi Chris !

    This is a nice tutorial, thank you. But I have a problem when I try to use this.

    I apply the OpacityMask to a Canvas. The corner are rounded, that’s good but… my window is white ! The mask don’t act as an OpacityMask but as a brush.
    However, I can always use my controls… if I find them ! :p

    Do you have experienced this type of problem in the past ?

    Regards,
    Michaël

    Reply

    1. Michaël – It sounds like your mask is defined after the content you’re masking… This technique relies on your content “covering” the mask shape (ie being declared after it in the XAML). Or it could be another issue entirely 🙂 Could you mail me some sample XAML? (blog at chriscavanagh.com)

      Reply

    2. Michaël – Thanks for the example 🙂 The problem there is your mask is also the container of your content. The mask needs to be a “sibling” of your content (rather than a parent of it). Generally I find the best way is to use a Grid as the container/parent, then inside that have the mask (with no width or height specified so it fills the container) followed by the content. The content then has its OpacityMask pointing to the mask element (via the VisualBrush). Let me know if you need some more examples and I’ll fire you an email 🙂 (the blog comments section doesn’t like markup).

      Hope this helps!

      Reply

      1. Oh yes, I’m so studip. On all the example that I have seen, I don’t have noticed that the border is not containing the element I want to apply the mask.
        I don’t know why, but I was SURE that the border contained the element on which I want to apply the mask. What a stupid mistake !

        Thank you for your help Chris. 🙂
        Regards,
        Michaël

  19. If you replace the border with this

    you will notice a small line and the left top between te yellow and the image which should not be there.

    Reply

  20. (my code was gone, 2nd attempt)
    If you replace the first border settings with
    BorderThickness=”20″
    CornerRadius=”60″
    Padding=”0″

    and the 2nd border mask with
    CornerRaduis=”50″
    You will notice a small black line at the left,top position between the yellow and the image

    Reply

  21. The only problem is that you have to round to a certain background color, which creates only an illusion of rounded corners. You can’t place the rounded corners image on a non-white surface (in this particular example). If you do, you will end up with your image being rounded with white corners on some different background.

    In my task I was to create an image from WPF object with rounded corners to place the image as an icon to notification area. This way I would get a dynamically generated notification area icon. This example doesn’t works in my particular case.

    Reply

  22. Nice solution. It should be noted that in some cases you can just use the background of the border. This is true for your example of putting an image inside the border, you can just set the border’s background to an imagebrush.

    I suspect this xaml will disappear but here is an example:

    Reply

    1. Yep it did 🙂

      Here is is without the gt and lts

      Border BorderThickNess=5 CornerRadius=10
      Border.BackGround
      ImageBrush ImageSource=”path to image here”
      Close Border.BackGround
      Close Border

      Reply

  23. Anyone has a solution/workaround for opacity mask not working correctly on 64 Bit machines (not always)?

    For example:

    If you put the XAML above in a Window, and compile your application as x64, then you will not see the text (or anything that is being masked).

    It happens on Zoom and Rotate (a combination of them), for each scaling there are specific angles that the mask doesn’t render correctly.

    Compiling my application as x86 is not an option.

    I’ve tried other solutions for creating a round rectangle border like this, but Opacity Mask is the only one that gives the desired result, but still, other working solutions will be welcome.

    Thanks

    Reply

  24. mmm, the XAML I’ve posted is not visible…

    Just have the XAML of this blog changed so the Grid will have RenderTransform with Render Group that contains RotateTransform of 45 deg, and then ScaleTransform of ScaleX and ScaleY equals to 2.

    Bug already reported by my here:
    http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/25c05f8e-b8b8-4103-a5cf-2c51fc2d4b78

    Sorry for the bad XAML on that example. That was after ripping all the XAMLs, DLLS, etc from the huge 40 Dlls project until I got a minimal project that simulates the problem (I only then discovered that the reason I couldn’t reproduce the problem on a clean solution was the fast that the new one was on x86… I didn’t know at that time that this problem is x64 related).

    Reply

  25. Hi,

    First of all, thanks for this solution. Been cracking my head on how to get rid of the ugly corners of my contents within a rounded control.

    But I’ve run into a weird (to me) issue when I tried to incorporate this into my project. I’m using a Selector, in which I have several “pages” which are essentially UserControls which I display one by one progressively. Something like a Wizard. All the pages utilizes your OpacityMask method to achieve the rounded corner effects. But somehow only the first page displayed is able to show the contents of the rounded control. In the subsequent pages, it’s almost like the the “mask” border is in front of the contents, or simply the contents are not displayed. I checked my codes numerous times, but cannot figure out why, coz all the codes are almost identical except for the contents.

    I’m hoping you might have some ideas as to how this can happen and how to resolve this… Would be so grateful for your help…

    Reply

  26. –Border BorderBrush=”Black” Width=”60″ Height=”60″ CornerRadius=”7″–
    –Border.Background–
    –ImageBrush ImageSource=”{Binding Path=Photo}” Stretch=”Fill” /–
    –/Border.Background–
    –/Border–

    Reply

  27. I use OpacityMask and VisualBrush for new project window Phone 8.1
    But report: the member “OpacityMask” is not recognized or is not accessible
    and the type “VisualBrush” was not found….
    now, what can I import for my project to use OpacityMask and VisualBrush?
    thanks!

    Reply

  28. A totally different approach, if you have a solid color background on the Window/page is to just create a bitmap that is a rectangle, and in the bitmap have the rounded corner effect that you want. I know that is a lot of hassle for just one button, but if you reuse it over and over it is not bad.

    Reply

Leave a comment