Click here to Skip to main content
15,881,882 members
Articles / Desktop Programming / WPF
Article

WPF PartiallyRoundedRectangle: Choose Which Corners You Want Rounded

Rate me:
Please Sign up or sign in to vote.
3.00/5 (3 votes)
26 Nov 2007CPOL2 min read 69.2K   32   10
This is my attempt at writing a new Shape class that allows the user to choose which corners of a rectangle should be rounded
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:

C#
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:

XML
<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)


Written By
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 Pin
batiati19-Feb-10 3:35
batiati19-Feb-10 3:35 
GeneralXAML Equivalent Pin
Vyas Bharghava6-Sep-09 19:19
Vyas Bharghava6-Sep-09 19:19 
GeneralDoubt in XAML [modified] Pin
Member 38034812-Jul-08 1:56
Member 38034812-Jul-08 1:56 
GeneralRe: Doubt in XAML Pin
chaiguy13372-Jul-08 2:52
chaiguy13372-Jul-08 2:52 
GeneralRe: Doubt in XAML Pin
Member 38034813-Jul-08 19:29
Member 38034813-Jul-08 19:29 
GeneralFixed... Working fine Pin
Member 38034814-Jul-08 0:34
Member 38034814-Jul-08 0:34 
GeneralWpf does support that. Pin
Patrick Klug26-Nov-07 16:12
Patrick Klug26-Nov-07 16:12 
GeneralRe: Wpf does support that. Pin
chaiguy133726-Nov-07 16:48
chaiguy133726-Nov-07 16:48 
GeneralRe: Wpf does support that. Pin
Stevie14-Sep-08 3:52
Stevie14-Sep-08 3:52 
GeneralRe: Wpf does support that. Pin
chaiguy133714-Sep-08 5:38
chaiguy133714-Sep-08 5:38 
Yes, certainly. There are a couple options. The first is to redefine button's ControlTemplate with a custom shape, using a custom rounded rect (either the Border class, or my class). The second, if you want it to actually match the look of the system buttons, is to use a tool like Snoop or Mole to dig into the visual tree of an existing button and copy how it does things. Either way you'll end up overriding the ControlTemplate. There may be other ways, though; WPF is a very flexible platform.

“Time and space can be a bitch.”
–Gushie, Quantum Leap

{o,o}.oO( Looking for a great RSS reader? Try FeedBeast! )
|)””’)            Built with home-grown CodeProject components!
-”-”-

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

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