Introduction
Devasted with hours of coding practices and hard-to-design Java applications,
I was tempted to try the Microsoft .Net Framework and found C# as a better
alternative to Java. Making Windows programs was even easier and I could see
endless possibilities for programs on the Windows Platform.
After-effects
of this switch left me wondering how I could create graphics on components and
forms and I somehow (read: hours of searches on CodeProject and the Framework
Documentation) came to the conclusion that the use of Graphics was a
necessity in my programs. Meanwhile, CodeProject was flooding with articles on
creating XP-like buttons and forms. More specifically, user controls that used
rounded paths and ellaborate designs. That's when I decided that I would create
a similar button with a Rounded Rectangle. "It's so simple", I thought to
myself.
Preliminaries
I sat down to create the button myself. Vague images, floated around in my
head, of how I would have accomplished this in Java. The most appropriate
implementation that I could think of was something like this (Oh! How I loved
Java):
import java.awt.*;
public class RoundButton extends Canvas
{
public RoundButton()
{
this.setSize(100, 20);
this.repaint();
}
public void paint(Graphics g)
{
g.setColor(new Color(200, 200, 200));
g.fillRoundRect(2, 2, this.getWidth()-4,
this.getHeight()-4, 5, 5);
g.setColor(new Color(60, 60, 60));
g.drawRoundRect(2, 2, this.getWidth()-4,
this.getHeight()-4, 5, 5);
}
}
The Horror
However, as I stepped further to demonstrate the same effect in C#, it was
then that I came face-to-face with sudden disbelief. The C# Graphics class that
I had praised so much lacked a method for a Rounded Rectangle. What!?! How
tedious would it be to create a Rounded Rectangle in such a situation. So, I
retreated back to where I had started: searching CodeProject. Though, I
found loads of code telling me how this could be implemented but all the codes
were built around some user control or component. I wanted a class that would
most probably inherit the methods and properties of the Graphics
class and include additional and extended functionality. But there was
still one problem: The Graphics
class was abstract
and/or sealed
(I don't know why I hate this word).
The Answers
Amidst results on Google [new window], I came across a forum named Drawing Rectangle
but with rounded corners... [new window] that lay there, waiting for a desparate soul to
encounter it's existence on the Mathew Reynold's .NET 247's Newsgroup [new window]. A certain
person, whom I would like to mention here, named Tim Overbay, provided with a
little routine to create such a Rounded Rectangle in VB.Net. This was just the
starting point I had needed to carry on with my very own implementation. I
copied the code, converted it to C# and there I had it. The final result: a
class that I now lovingly dub as the ExtendedGraphics
class. The
complete code is present here as follows.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace System.Drawing.Extended
{
public class ExtendedGraphics
{
private Graphics mGraphics;
public Graphics Graphics
{
get{ return this.mGraphics; }
set{ this.mGraphics = value; }
}
public ExtendedGraphics(Graphics graphics)
{
this.Graphics = graphics;
}
#region Fills a Rounded Rectangle with integers.
public void FillRoundRectangle(System.Drawing.Brush brush,
int x, int y,
int width, int height, int radius)
{
float fx = Convert.ToSingle(x);
float fy = Convert.ToSingle(y);
float fwidth = Convert.ToSingle(width);
float fheight = Convert.ToSingle(height);
float fradius = Convert.ToSingle(radius);
this.FillRoundRectangle(brush, fx, fy,
fwidth, fheight, fradius);
}
#endregion
#region Fills a Rounded Rectangle with continuous numbers.
public void FillRoundRectangle(System.Drawing.Brush brush,
float x, float y,
float width, float height, float radius)
{
RectangleF rectangle = new RectangleF(x, y, width, height);
GraphicsPath path = this.GetRoundedRect(rectangle, radius);
this.Graphics.FillPath(brush, path);
}
#endregion
#region Draws a Rounded Rectangle border with integers.
public void DrawRoundRectangle(System.Drawing.Pen pen, int x, int y,
int width, int height, int radius)
{
float fx = Convert.ToSingle(x);
float fy = Convert.ToSingle(y);
float fwidth = Convert.ToSingle(width);
float fheight = Convert.ToSingle(height);
float fradius = Convert.ToSingle(radius);
this.DrawRoundRectangle(pen, fx, fy, fwidth, fheight, fradius);
}
#endregion
#region Draws a Rounded Rectangle border with continuous numbers.
public void DrawRoundRectangle(System.Drawing.Pen pen,
float x, float y,
float width, float height, float radius)
{
RectangleF rectangle = new RectangleF(x, y, width, height);
GraphicsPath path = this.GetRoundedRect(rectangle, radius);
this.Graphics.DrawPath(pen, path);
}
#endregion
#region Get the desired Rounded Rectangle path.
private GraphicsPath GetRoundedRect(RectangleF baseRect,
float radius)
{
if( radius<=0.0F )
{
GraphicsPath mPath = new GraphicsPath();
mPath.AddRectangle(baseRect);
mPath.CloseFigure();
return mPath;
}
if( radius>=(Math.Min(baseRect.Width, baseRect.Height))/2.0)
return GetCapsule( baseRect );
float diameter = radius * 2.0F;
SizeF sizeF = new SizeF( diameter, diameter );
RectangleF arc = new RectangleF( baseRect.Location, sizeF );
GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
path.AddArc( arc, 180, 90 );
arc.X = baseRect.Right-diameter;
path.AddArc( arc, 270, 90 );
arc.Y = baseRect.Bottom-diameter;
path.AddArc( arc, 0, 90 );
arc.X = baseRect.Left;
path.AddArc( arc, 90, 90 );
path.CloseFigure();
return path;
}
#endregion
#region Gets the desired Capsular path.
private GraphicsPath GetCapsule( RectangleF baseRect )
{
float diameter;
RectangleF arc;
GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
try
{
if( baseRect.Width>baseRect.Height )
{
diameter = baseRect.Height;
SizeF sizeF = new SizeF(diameter, diameter);
arc = new RectangleF( baseRect.Location, sizeF );
path.AddArc( arc, 90, 180);
arc.X = baseRect.Right-diameter;
path.AddArc( arc, 270, 180);
}
else if( baseRect.Width < baseRect.Height )
{
diameter = baseRect.Width;
SizeF sizeF = new SizeF(diameter, diameter);
arc = new RectangleF( baseRect.Location, sizeF );
path.AddArc( arc, 180, 180 );
arc.Y = baseRect.Bottom-diameter;
path.AddArc( arc, 0, 180 );
}
else
{
path.AddEllipse( baseRect );
}
}
catch(Exception ex)
{
path.AddEllipse( baseRect );
}
finally
{
path.CloseFigure();
}
return path;
}
#endregion
}
}
What does the code do?
The above code is a simple class that could be used in your projects for
additional drawing routines. What I tried to do here is that I created the class
that accepts a Graphics
class object. Why I did this is a harsher
punishment I got because the Graphics class was sealed and it could not be
inherited. Otherwise, I would have simply had inherited my class with the
Graphics
class.
There are two private methods in the class and four public methods. The two
private methods namely the GetRoundedRect(...)
and the
GetCapsule(...)
method are of the most importance. Both these
methods draw four lines connected with rounded arcs to form a Rounded Rectangle.
If the rectangle's height or width is less than the diameter of arc specified,
then the method instead of providing the user with a rectangular shape, gives
the user a capsule obtained through the GetCapsule(...)
method. If
both the width and height are less than the diameter or the arc and are of the
same size, then a circular ellipse is obtained from the method.
To make the class more useful and familiar, four methods are added to the
class that resemble methods found in the Graphics
class.
What's Next...
Think of this article as a first in a series of articles, because I would be
using this same class in some of my upcoming tutorials on how to create buttons
and effective user interface. I would be grateful to anyone who gives me advice
on how I could optimize the above code and any suggestions. I am working on an
implementation for even-sided polygons and will include those snippets and
methods in this class in the near future.