Figure 1.1: Select rounded corners in antialias output
The figure shows an implementation of three rounded rectangles to make up this effect.
Note that with the current version of the code, you can select the corners you want to round.
Table of contents
Introduction
My obsession with the incomplete nature of the System.Drawing.Graphics
class in the .NET Framework's Base Class Library began with the article here on
CodeProject, which was titled “Extended Graphics
– An implementation of Rounded Rectangle in C#” back in 2003.
I chose the class after looking at some code in Java. The
System.Drawing.Graphics
class would not do certain things that similar
classes in other language APIs could do. Most of my attention and focus was
fixated on the one feature that I truly required – that being the ability to
produce rectangles with rounded corners. And, to be honest, I was successful in
creating an ad-hoc class that could pertain to my needs.
A fresh approach to an old problem
Six years after my initial endeavours, C# has grown both
in functionality and extensibility. Alas, we are left with the same predicament,
that of not having a proper implementation of the same features I longed for back
then. I enlisted the help of my readers to share with me suggestions of other missing
functionality from the GDI+ System.Drawing.Graphics
class. While writing this code, I
tried to include as much of the missing functionality as I could based on those very
suggestions.
In fact, I didn't have to look further than a few forums scattered throughout the
Internet to find some other things that needed to be implemented in this updated
installment of the code I wrote six years ago. This time however, C# 3.0 provided
me with an excellent and a fresh approach to writing my code. I eventually planned
on using the one new feature I loved the most in C# 3.0 – the
Extension Methods. Read ahead
to know what extension methods are and what they really do.
What are extension methods?
Sometimes you want to add methods to existing classes (or types), ones that you do have
access to directly. For instance, you might wonder if there was a way of extending the
.NET Framework's base classes like System.String
(or in our case, the
System.Drawing.Graphics
class). Well you'd be amazed to know that there is
a certain way of doing that. That's where the extension methods come handy.
Extension methods are a way of extending methods for an existing type without creating
an extended type. So, for instance, one can add methods to the classes already present
in the .NET Framework without, say, creating a subclass for that type. Throughout this
article, I would try my best to explain how extension methods are used to extend the
functionality of the already existing System.Drawing.Graphics
class. Because
this article is not about extension methods, I would refrain from going deeper into a
proper conversation about extension methods.
Extension methods in action
We will begin this article by looking at an example of an extension method. Below is a
code snippet that tells the compiler to include the code to the System.String
class. If you note carefully there are a few things about the code below that makes it
different than your average code. First and foremost, notice the static
class definition. Then notice the third line with the code this string
. I'll
try to explain what happens when you run this code in a moment.
Listing 1.1
static class StringExtension
{
public static string Reverse(this string text)
{
}
}
Once the code above has been complete, built and compiled, the mere inclusion of this file
to your project would automatically add the created Reverse()
method to the
System.String
class. The method can hence be called with every object of the
System.String
class as such:
Listing 1.2
...
string text = "HELLO WORLD";
Console.WriteLine( text.Reverse() );
...
Note that when I called the Reverse()
method, I did not give it the parameter
as was made obvious in Listing 1.1. The reasoning for it is quite straight-forward and simple.
Remember the this string
code – it turns out that code is the real deal
when it comes to extension methods. That code itself tells the compiler to attach the
method definition to a certain class. In our case, it was the System.String
class. You can only realise the true potential of extension methods when you see, code like
the following being made possible.
Listing 1.3
...
string text = "ARUN".Reverse();
...
The old method and the new approach
In the previous installments of this code, I created a new wrapper class for the
System.Drawing.Graphics
class and named it ExtendedGraphics
. Being
a wrapper class, it encapsulated all the functionality of the inherited parent with some
added features. Below is a sample of how the process worked:
Listing 1.4
...
System.Drawing.Graphics g = this.CreateGraphics();
ExtendedGraphics eg = new ExtendedGraphics(g);
eg.FillRoundedRectangle(brush, x, y, width, height, arcRadius);
...
You couldn't create a rounded rectangle with the System.Drawing.Graphics
class,
so you had to wrap the ExtendedGraphics
around it to provide the missing
functionality. The only problem with the above code was the actual creation of a new object.
People had to remember the exact method calls for the new class and had to unwillingly add
the class to the project's using
directives. Wouldn't it have been much simpler
if one could do the following:
Listing 1.5
...
System.Drawing.Graphics g = this.CreateGraphics();
g.FillRoundedRectangle(brush, x, y, width, height, arcRadius);
...
Simple, and sweet! Guess what?! This new implementation of the class does just that for you.
You just have to add the class to your project and just like that start using the already
present base class library for
System.Drawing.Graphics
to your work. No need
to make fancy wrapper classes or extra objects.
Code enhancements
With the possibility of extending any .NET Framework base class, it suddenly struck with
this idea of extending the current System.Drawing.Graphics
class and I sat one
day and did just that. When I was finished with the initial implementation, I couldn't
have been happier with the result. The new implementation was not only faster, but was also
much cleaner and readable. Reducing the overhead by not creating yet another object-wrapper
and instead just using an extended version of an already optimised class certainly gave
this version an appeal.
How to use this code in your projects?
Download the source zip file above and extract the GraphicsExtension.cs
file into your project. Once the file has been included in your project, you are almost
half-way through. To use the features of this class in your projects, simply add a
using
directive on top of every code file that requires the code like this:
Listing 1.6
using System;
using System.Drawing;
using Plasmoid.Extensions;
...
Once you've added the directive as explained, all occurrences of the
System.Drawing.Graphics
file will automatically be extended with the brand
new functionality in the code. Whenever you use an object of the class throughout your
code, you will have the IntelliSense detect the new methods for you. But remember, the
GraphicsExtension
isn't the only class you get with this implementation.
There are some fancy new things that you can do with your code. Let's look at some of
them now.
Figure 1.2: The Extended Graphics Test Suite bundle
If you would rather play around with the output before diving deep into code, download the test suite and familiarise yourself with the concepts around the issues addressed by this project.
Creating rounded rectangles
Creating rounded rectangles could never have been much easier. The following code
shows how to create a simple rounded rectangle with all the corners rounded.
Listing 1.8
...
System.Drawing.Graphics g = this.CreateGraphics();
g.FillRoundedRectangle(brush, x, y, width, height, arcRadius);
...
..where
x
and
y
are the axes to start drawing the rectangle
at,
width
and
height
are what their names suggest. The
brush
is an instance of the
System.Drawing.Brush
object that
would define the colour of the rectangle's fill and finally
arcRadius
which is the radius of the arc that creates all the rounded edges of the rectangle.
You probably would never need to remember this method ever again as it would always
come up in the IntelliSense for the
System.Drawing.Graphics
class.
Just like the FillRoundedRectangle(..)
method, you can also use another
method offered to create the border of a rounded rectangle only. Following is the
code for the generation of a border, where g
is an object of the
System.Drawing.Graphics
class and pen
is an object of the
System.Drawing.Pen
class.
Listing 1.9
...
g.DrawRoundedRectangle(pen, x, y, width, height, arcRadius);
...
New feature added to VER 1.0.0.2 |
If however, you want to round only a select corner edge or even more than one edges
of the rectangle, you can specify the RectangleEdgeFilter
enum for that very
purpose. A RectangleEdgeFilter
enum holds only six values:
None = 0
TopLeft = 1
TopRight = 2
BottomLeft = 4
BottomRight = 8
All = TopLeft|TopRight|BottomLeft|BottomRight
Using these one can write code to produce the effect of partially round edges where only
some of the edges or corners of the rectangle would be rounded. For instance, if I were
to round only the TopLeft
and the BottomRight
corners, I
would write the following code:
Listing 2.0
...
g.FillRoundedRectangle(brush, x, y, width, height, arcRadius, RectangleEdgeFilter.TopLeft | RectangleEdgeFilter.BottomRight);
...
Obtaining information about fonts
New feature added to VER 1.0.0.4 |
New to the code in version 1.0.0.4 is the inclusion of the FontMetrics
class
which works hand-in-hand with the System.Drawing.Graphics
class to present
you with vital information about each individual font. The following code demonstrates how
easy it is to obtain some very information about a font, where font
is an
object of the System.Drawing.Font
class.
Listing 2.1
...
FontMetrics fm = g.GetFontMetrics(font);
fm.Height;
fm.Ascent;
fm.Descent;
fm.InternalLeading;
fm.ExternalLeading;
fm.AverageCharacterWidth;
fm.MaximumCharacterWidth;
fm.Weight;
fm.Overhang;
fm.DigitizedAspectX;
fm.DigitizedAspectY;
...
With such vital information in grasp, UI developers amongst us can take full advantage of
this method by aptly applying this class to determine font boundaries and overall structure
of the laid-out text for controls based on DrawString(..)
outputs.
Figure 1.3: Explaining font metrics
This figures shows in detail the various font metrics two lines of text can exhibit. It is very crucial to get the details right if you are doing anything even remotely related to typography. In order to get that perfect feel and readability, one must know these basics. All sizes measured by FontMetrics
class are calculated as em
s.
History
- Ver 1.0.0.1 (20 Jul 2009)
- Initial release with modified code using Extension Methods as ported from the previous
CodeProject article.
- Ver 1.0.0.2 (25 Jul 2009)
- Added the
RectangleEdgeFilter
enumeration to facilitate rounding of
selected edges.
- Modified code to always adapt to an anti-aliased output when drawing and filling
rounded rectangles.
- Ver 1.0.0.3 (26 Jul 2009)
- Added support for
Rectangle
and RectangleF
objects for
method parameters for DrawRoundedRectangle
and
FillRoundedRectangle
.
- Improved performance in some places by eliminating objects created within loops for
instance.
- Ver 1.0.0.4 (27 Jul 2009)
- Added
FontMetrics
class to be used to measure font heights, ascents,
descents, leading, etc.
Conclusion
The above examples illustrates clearly the benefits of using extension methods to extend functionalities in an existing .NET class (in this case, the System.Drawing.Graphics
class). This significantly reduces the time spent in creating and managing separate inherited objects.