Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Extended Graphics - Rounded rectangles, Font metrics and more for C# 3.0

0.00/5 (No votes)
26 Jul 2009 1  
Adding missing functionality to the Graphics class with the use of Extension Methods in C# 3.0.

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)
    {
        // Logic for string reversal goes here
    }
}

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() );     // This line should now print: DLROW OLLEH
...

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();          // This should put "NURA" in the variable 'text'.
...

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;               // this is how the directive should appear
...

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:

  1. None = 0
  2. TopLeft = 1
  3. TopRight = 2
  4. BottomLeft = 4
  5. BottomRight = 8
  6. 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;                 // Gets a font's height
    fm.Ascent;                 // Gets a font's ascent
    fm.Descent;                // Gets a font's descent
    fm.InternalLeading;        // Gets a font's internal leading
    fm.ExternalLeading;        // Gets a font's external leading
    fm.AverageCharacterWidth;  // Gets a font's average character width
    fm.MaximumCharacterWidth;  // Gets a font's maximum character width
    fm.Weight;                 // Gets a font's weight
    fm.Overhang;               // Gets a font's overhang
    fm.DigitizedAspectX;       // Gets a font's digitized aspect (x-axis)
    fm.DigitizedAspectY;       // Gets a font's digitized aspect (y-axis)
...

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 ems.

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here