Monday, January 25, 2010

How to draw a rounded rectangle in .NETCF

In this short article I'd like to demonstrate how to draw rounded rectangles by P/Invoking the GDI function RoundRect. Let's create an extension method called FillRoundedRectangle to the Graphics class.

In order to use the function we need to create a few GDI objects: a Pen to draw the border, and a Brush to fill the rectangle. We will mostly use P/Invoke for creating and releasing GDI objects

const int PS_SOLID = 0;
const int PS_DASH = 1;
 
[DllImport("coredll.dll")]
static extern IntPtr CreatePen(int fnPenStyle, int nWidth, uint crColor);
 
[DllImport("coredll.dll")]
static extern int SetBrushOrgEx(IntPtr hdc, int nXOrg, int nYOrg, ref Point lppt);
 
[DllImport("coredll.dll")]
static extern IntPtr CreateSolidBrush(uint color);
 
[DllImport("coredll.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobject);
 
[DllImport("coredll.dll")]
static extern bool DeleteObject(IntPtr hgdiobject);
 
[DllImport("coredll.dll")]
static extern bool RoundRect(
    IntPtr hdc, 
    int nLeftRect, 
    int nTopRect, 
    int nRightRect, 
    int nBottomRect, 
    int nWidth, 
    int nHeight);

The CreateSolidBrush function in native code actually takes a COLORREF parameter, and the developer would normally use the RGB macro to create it. We need to translate that macro into a .NET function

static uint GetColorRef(Color value)
{
    return 0x00000000 | ((uint)value.B << 16) | ((uint)value.G << 8) | (uint)value.R;
}

Now we have our P/Invoke definitions in place we can neatly wrap all P/Invoke operations in a single function and let's call that FillRoundedRectangle()

public static void FillRoundedRectangle(
    this Graphics graphics,
    Pen border,
    Color color,
    Rectangle rectangle,
    Size ellipseSize)
{
    var lppt = new Point();
    var hdc = graphics.GetHdc();
    var style = border.DashStyle == DashStyle.Solid ? PS_SOLID : PS_DASH;
    var hpen = CreatePen(style, (int)border.Width, GetColorRef(border.Color));
    var hbrush = CreateSolidBrush(GetColorRef(color));
 
    try
    {
        SetBrushOrgEx(hdc, rectangle.Left, rectangle.Top, ref lppt);
        SelectObject(hdc, hpen);
        SelectObject(hdc, hbrush);
 
        RoundRect(hdc, 
                  rectangle.Left, 
                  rectangle.Top, 
                  rectangle.Right, 
                  rectangle.Bottom, 
                  ellipseSize.Width, 
                  ellipseSize.Height);
    }
    finally
    {
        SetBrushOrgEx(hdc, lppt.Y, lppt.X, ref lppt);
        DeleteObject(hpen);
        DeleteObject(hbrush);
 
        graphics.ReleaseHdc(hdc);
    }
}

Using this extension method should be straight forward, but here's an example where "e" is an instance of PaintEventArgs:

using (var pen = new Pen(Color.Blue))
    e.Graphics.FillRoundedRectangle(
        pen, 
        Color.LightBlue, 
        new Rectangle(10, 10, 100, 100), 
        new Size(16, 16));


I hope you found this useful. If you're interested in the full source code then you can grab it here

3 comments:

Anonymous said...

Hola Christian, muy interesante los codigos. me fueron de una gran ayuda. Gracias

canvas wall art said...

Students often find it difficult to determine how to draw an arm that extends away from a model’s body or the distance between two objects sitting on a table.

Sven G said...

Great, thank you