Click here to Skip to main content
Click here to Skip to main content

WPF PartiallyRoundedRectangle: Choose Which Corners You Want Rounded

By , 26 Nov 2007
Rate this:
Please Sign up or sign in to vote.
Screenshot - Tab-Like.png

Introduction

I'm new to WPF, so this is my first attempt at a reusable solution to a problem I had: although WPF is ridiculously powerful and customizable, for some reason Microsoft didn't build-in any logic to allow a rounded rectangle to be only rounded on certain corners, like you might use for a tab at the top or side of a window for example.

Background

I'm still very new to WPF and there's so much to wrap my head around, I'm sure this probably isn't the best solution. As such, I welcome constructive comments for the benefit of myself and anyone else interested in this code.

Consider this article a learning experience.

Using the Code

Here is the code, in its entirety:

public class PartiallyRoundedRectangle : Shape {
    public static readonly DependencyProperty RadiusXProperty;
    public static readonly DependencyProperty RadiusYProperty;

    public static readonly DependencyProperty RoundTopLeftProperty;
    public static readonly DependencyProperty RoundTopRightProperty;
    public static readonly DependencyProperty RoundBottomLeftProperty;
    public static readonly DependencyProperty RoundBottomRightProperty;

    public int RadiusX {
        get { return (int)GetValue(RadiusXProperty); }
        set { SetValue(RadiusXProperty, value); }
    }

    public int RadiusY {
        get { return (int)GetValue(RadiusYProperty); }
        set { SetValue(RadiusYProperty, value); }
    }

    public bool RoundTopLeft {
        get { return (bool)GetValue(RoundTopLeftProperty); }
        set { SetValue(RoundTopLeftProperty, value); }
    }

    public bool RoundTopRight {
        get { return (bool)GetValue(RoundTopRightProperty); }
        set { SetValue(RoundTopRightProperty, value); }
    }

    public bool RoundBottomLeft {
        get { return (bool)GetValue(RoundBottomLeftProperty); }
        set { SetValue(RoundBottomLeftProperty, value); }
    }

    public bool RoundBottomRight {
        get { return (bool)GetValue(RoundBottomRightProperty); }
        set { SetValue(RoundBottomRightProperty, value); }
    }

    static PartiallyRoundedRectangle() {
        RadiusXProperty = DependencyProperty.Register
            ("RadiusX", typeof(int), typeof(PartiallyRoundedRectangle));
        RadiusYProperty = DependencyProperty.Register
            ("RadiusY", typeof(int), typeof(PartiallyRoundedRectangle));

        RoundTopLeftProperty = DependencyProperty.Register
            ("RoundTopLeft", typeof(bool), typeof(PartiallyRoundedRectangle));
        RoundTopRightProperty = DependencyProperty.Register
            ("RoundTopRight", typeof(bool), typeof(PartiallyRoundedRectangle));
        RoundBottomLeftProperty = DependencyProperty.Register
            ("RoundBottomLeft", typeof(bool), typeof(PartiallyRoundedRectangle));
        RoundBottomRightProperty = DependencyProperty.Register
            ("RoundBottomRight", typeof(bool), typeof(PartiallyRoundedRectangle));
    }

    public PartiallyRoundedRectangle() {
    }

    protected override Geometry DefiningGeometry {
        get {
            Geometry result = new RectangleGeometry
            (new Rect(0, 0, base.Width, base.Height), RadiusX, RadiusY);
            double halfWidth = base.Width / 2;
            double halfHeight = base.Height / 2;

            if (!RoundTopLeft)
                result = new CombinedGeometry
            (GeometryCombineMode.Union, result, new RectangleGeometry
            (new Rect(0, 0, halfWidth, halfHeight)));
            if (!RoundTopRight)
                result = new CombinedGeometry
            (GeometryCombineMode.Union, result, new RectangleGeometry
            (new Rect(halfWidth, 0, halfWidth, halfHeight)));
            if (!RoundBottomLeft)
                result = new CombinedGeometry
            (GeometryCombineMode.Union, result, new RectangleGeometry
            (new Rect(0, halfHeight, halfWidth, halfHeight)));
            if (!RoundBottomRight)
                result = new CombinedGeometry
            (GeometryCombineMode.Union, result, new RectangleGeometry
            (new Rect(halfWidth, halfHeight, halfWidth, halfHeight)));

            return result;
        }
    }
} 

How It Works

The design is very simple: we just derive a new class from Shape, implement a few DependencyProperties such as RadiusX and RadiusY which would normally be defined on Rectangle. Since Rectangle is marked sealed, we cannot inherit from it directly.

The most important properties are of course the RoundXXX where XXX is the corner. When these are false, a RectangleGeometry is unioned overtop the offending round corner so as to give it its point back. The rectangles cover the entire quarter of the shape, so it should work with any setting of RadiusX/RadiusY.

Here's what an instance of this class looks like in XAML:

<custom:PartiallyRoundedRectangle RoundTopLeft="True" 
    RoundBottomRight="True" RadiusX="20" RadiusY="20" 
    Fill="LightBlue" Height="112" Width="200" />

This sets the top-left and bottom-right corners to be rounded (the default is false):

Screenshot - Shape.png

Points of Interest

I am still not super clear on separating procedural code from declarative code, and the WPF libraries make this really confusing. For example, it would have been nice if Geometry simply provided a Union() method that accepted another Geometry. Instead, I've had to keep recursively creating new CombinedGeometry's and I fear these are going to be kept in memory whenever a shape is drawn. An alternative might be to call one of the Flatten__ methods before returning the Geometry.

This implementation has issues with stroke for some reason that I am unsure of. Although you can define a stroke, it ends up looking really bad and unbalanced compared to the native Rectangle. Furthermore, AttachedProperties like BitmapEffect don't seem to work. Like I said, I'm brand new to this so any insight would be appreciated.

The Stroke Problem

Screenshot - StrokeIssue.png

History

  • 11/26/2007: Updated to include screenshots

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

chaiguy1337
President The Little Software Company
Canada Canada
My name is Logan Murray and I'm a Canadian. I'm interested primarily in C# and Windows desktop application development (learning WPF at the moment and then hopefully LINQ), though I hope to branch-out more to the web side of things eventually. I am the president and owner of The Little Software Company and am currently working on the Windows version of OrangeNote, to be released soon. Check out my RSS reader, FeedBeast™.

Comments and Discussions

 
GeneralUser "Border" element instead Pinmemberbatiati19-Feb-10 3:35 
GeneralXAML Equivalent PinmemberVyas Bharghava6-Sep-09 19:19 
GeneralDoubt in XAML [modified] PinmemberMember 38034812-Jul-08 1:56 
GeneralRe: Doubt in XAML Pinmemberlogan13372-Jul-08 2:52 
GeneralRe: Doubt in XAML PinmemberMember 38034813-Jul-08 19:29 
GeneralFixed... Working fine PinmemberMember 38034814-Jul-08 0:34 
GeneralWpf does support that. PinmemberPakl26-Nov-07 16:12 
GeneralRe: Wpf does support that. Pinmemberlogan133726-Nov-07 16:48 
GeneralRe: Wpf does support that. PinmemberStevie14-Sep-08 3:52 
GeneralRe: Wpf does support that. Pinmemberchaiguy133714-Sep-08 5:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 26 Nov 2007
Article Copyright 2007 by chaiguy1337
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid