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

A Beginner’s Primer on Drawing Graphics using the .NET Framework

0.00/5 (No votes)
22 Aug 2009 1  
A primer to help launch the beginner with drawing graphics.

Introduction

Admittedly, the Windows Presentation Foundation (WPF) has established itself as a powerful technology for graphics, amongst other things. This does not mean, however, that the beginner cannot benefit from the .NET Framework 2.0’s provision for drawing graphics. Graphics in .NET begins with drawing lines and shapes, and continues on with work on images and formatting text. Drawing begins with the System.Drawing.Graphics class. To create an instance, you typically call a control’s CreateGraphics method. Alternatively, you can create a Graphics object based on an Image object if you want to save a picture as a file. Once you create the Graphics object, you have many methods you can use to perform the drawing:

  • Clear: clears the entire surface and fills it with color.
  • DrawEllipse: draws an ellipse or circle defined by a bounding rectangle specified by a pair of coordinates, a height, and width.
  • DrawIcon and DrawUnstretched: draw the image represented by the specified icon at the specified coordinates, with or without scaling the icon.
  • DrawImage: draws the specified Image object at the specified location.
  • DrawLine: draws a line connecting two points by the coordinate pairs.
  • DrawLines: draws a series of line segments that connect an array of Point structures.
  • DrawPath: draws a series of connected lines and curves.
  • DrawPie: draws a pie shape defined by an ellipse specified by a coordinate pair, a width, a height, and two radial lines.
  • DrawPolygon: draws a shape with three or more sides as defined by an array of Point structures
  • DrawRectangle: draws a rectangle or square, specified by a coordinate pair, a width, and a height.
  • DrawString: draws the specified text string at the specified location with the specified Brush and Font objects.

To use any of these methods, you must provide an instance of the Pen class. Typically, you specify the Pen class’ color and width in pixels. For example, the following code draws a 7-pixel wide red line from the upper left corner (1,1) to a point near the middle of the form (100, 100), as shown in figure below:

1.JPG

To run this code, create a Windows Forms application and add the code to a method run during the form’s Paint event:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
    private System.ComponentModel.Container components;
    public MainForm()
    {
        InitializeComponent();
        CenterToScreen();
        SetStyle(ControlStyles.ResizeRedraw, true); 
    }

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }
    private void InitializeComponent()
    {
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
        this.ClientSize = new System.Drawing.Size(292, 273);
        this.Text = "Fun with graphics";
        this.Resize += new System.EventHandler(this.Form1_Resize);
        this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
    }
    [STAThread]
    static void Main() 
    {
        Application.Run(new MainForm());
    }

    private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
        Graphics g = this.CreateGraphics();
        // Make a big red pen.
        Pen p= new Pen(Color.Red, 7);
         g.DrawLine(p, 1, 1, 100, 100);
    }

    private void Form1_Resize(object sender, System.EventArgs e)
    {
    }
}

Similarly, the following code draws a blue pie shape with a 60 degree angle, as shown below:

2.JPG

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
    private System.ComponentModel.Container components;
    public MainForm()
    {
        InitializeComponent();
        CenterToScreen();
        SetStyle(ControlStyles.ResizeRedraw, true); 
    }

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }

    private void InitializeComponent()
    {
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
        this.ClientSize = new System.Drawing.Size(292, 273);
        this.Text = "Fun with graphics";
        this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
    }

    [STAThread]
    static void Main() 
    {
        Application.Run(new MainForm());
    }

    private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
        Graphics g = this.CreateGraphics();
        Pen p = new Pen(Color.Blue, 3);
        g.DrawPie(p, 1, 1, 100, 100, -30, 60);
    }
}

GDI+

GDI stands for Graphical Device Interface, and the GDI+ library contains those classes that allow you to accomplish all types of rendering operations: rendering of lines, curves, gradients, display images… GDI+ also allows you to draw geometric shapes such as Bezier curves. Again, we acquire an instance of the System.Drawing.Graphics class by calling the CreateGraphics method of the Control or Form class. It is important to call the Dispose( ) method as soon as possible of the Graphics class obtained by the call to CreateGraphic( ).

The System.Drawing.Pen class

As we have seen, in the same way as an artist needs a pen to draw a curve, the methods of the Graphics class are used to draw lines, curves, or the outline of shapes; all we need is an instance of System.Drawing.Pen. Here is the code that draws a Bezier curve. You can specify the thickness of the stroke using the Width property of the Pen class. In addition, you can indicate if you want the stroke to be full, dashed, or dotted using the DashStyle property of the Pen class. You can also specify which type of drawing must occur at the ends of the stroke with the StartCap and EndCap properties of the Pen class:

3.JPG

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

public class MainForm : System.Windows.Forms.Form
{
    private System.ComponentModel.Container components;
    public MainForm()
    {
        InitializeComponent();
        CenterToScreen();
        SetStyle(ControlStyles.ResizeRedraw, true); 
    }

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }

    private void InitializeComponent()
    {
        this.AutoScaleBaseSize = new System.Drawing.Size(8, 17);
        this.ClientSize = new System.Drawing.Size(292, 273);
        this.Text = "Fun with graphics";
        this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
    }

    [STAThread]
    static void Main() 
    {
        Application.Run(new MainForm());
    }

    private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
        using ( Graphics g = CreateGraphics() )
        {
            Pen pen = new Pen( Color.Black);
            pen.Width = 5;
            pen.DashStyle = DashStyle.Dash;
            pen.StartCap = LineCap.RoundAnchor;
            pen.EndCap = LineCap.ArrowAnchor;
            g.DrawBezier( pen, new Point(10, 30), new Point(30, 200), 
                          new Point(50, -100), new Point(70, 100));
        }
    }
}

Working with Images

The Image and Bitmap Classes

The System.Drawing.Image abstract class enables you to create, load, modify, and save images such as BMP files, JPG files, and TIFF files. The Image class is abstract, but you can create instances of the class using Image.FromFile (which accepts a path to the image file as a parameter) and Image.FromStream (which accepts a System.IO.Stream object as a parameter). To display an image that is saved to the disk in a form (or as a background for a form or control), use the Graphics.DrawImage( ) method. Here is an example of an image that is saved to my desktop (on a Vista terminal, so the path is c:\users\dave\desktop\image.jpg):

4.JPG

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
    private System.ComponentModel.Container components;
    public MainForm()
    {
        InitializeComponent();
        CenterToScreen();
        SetStyle(ControlStyles.ResizeRedraw, true); 
    }

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }

    private void InitializeComponent()
    {
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
        this.ClientSize = new System.Drawing.Size(292, 273);
        this.Text = "Fun with graphics";
        
        this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
    }

    [STAThread]
    static void Main() 
    {
        Application.Run(new MainForm());
    }

    private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
        Bitmap bm = new Bitmap(@"C:\users\dave\desktop\imageA.jpg");
        Graphics g = this.CreateGraphics();
        g.DrawImage(bm, 1, 1, this.Width, this.Height);
    }
}

How to Create and Save Pictures

To create a new, blank picture, create an instance of the Bitmap class with one of the constructors that does not require an existing image. You can then edit it using the Bitmap.SetPixel method, or you can call Graphics.FromImage and edit the image using the Graphics drawing methods. To save the picture, call Bitmap.Save. The code shown below creates a blank 600 by 600 Bitmap, creates a Graphics object based on the Bitmap, uses the Graphics.FillPolygon and Graphics.DrawPolygon methods to draw a shape in the Bitmap, and then saves it to a file called bm.jpg in the current directory (the .NET Framework 2.0 directory). Like the code above, while many parts are repetitive, it will be run on the console command line. It requires the System.Drawing2D and System.Drawing.Imaging namespaces:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
    private System.ComponentModel.Container components;
    public MainForm()
    {
        InitializeComponent();
        CenterToScreen();
        SetStyle(ControlStyles.ResizeRedraw, true); 
    }
    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }
    private void InitializeComponent()
    {
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
        this.ClientSize = new System.Drawing.Size(292, 273);
        this.Text = "Fun with graphics";
        this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
    }
    [STAThread]
    static void Main() 
    {
        Application.Run(new MainForm());
    }

    private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
        Bitmap bm = new Bitmap(600, 600);  
        Graphics g = Graphics.FromImage(bm);
        Brush b = new LinearGradientBrush(new Point(1,1), 
                  new Point(600, 600), Color.White, Color.Red);
        Point[] points = new Point[] { new Point(77, 500), new Point(590, 100), 
                         new Point(250, 590), new Point(300, 410)};
        g.FillPolygon(b, points);
        bm.Save("bm.jpg", ImageFormat.Jpeg);
    }
}

The way this code is written, it will compile and then execute to output a plain form. However, if you type “bm.jpg”, you will find that you have created a file that is actually saved as a JPEG file. The size shown below has been reduced:

5.jpg

How to Use Icons

Icons are transparent bitmaps of specific sizes that are used by Windows to convey status. The .NET Framework provides standard 40 by 40 system icons as properties of the SystemIcons class. The simplest way to add an icon is to call the Graphics.DrawIcon method:

6.JPG

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
    private System.ComponentModel.Container components;
    public MainForm()
    {
        InitializeComponent();
        CenterToScreen();
        SetStyle(ControlStyles.ResizeRedraw, true); 
    }

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }

    private void InitializeComponent()
    {
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
        this.ClientSize = new System.Drawing.Size(292, 273);
        this.Text = "Fun with graphics";
        this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
    }

    [STAThread]
    static void Main() 
    {
        Application.Run(new MainForm());
    }

    private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
        Graphics g = this.CreateGraphics();
        g.DrawIcon(SystemIcons.Question, 40, 40);
    }
}

Things to do in Graphics Land when You’ve had Enough

Graphics applications can have functionality, but this requires taking a look at some of the core events of the Control class:

  • Click, DoubleClick: the Control class defines numerous events triggered in response to mouse input.
  • MouseEnter, MouseLeave, MouseDown, MouseUp, MouseMove, MouseHover, MouseWheel.
  • KeyPress, KeyUp, KeyDown: the Control class defines numerous events triggered in response to keyboard input.

This application is meant to show how Windows Forms responds to standard input. Run it on the command line. It give a pop-up box for every key pressed, and gives the coordinates of every mouse location. It will also output a message box for every type of mouse click or keypress. We first start by building a new Form class (which we have done – MainForm), set the initial size of the form to some arbitrary dimensions, and override the Dispose() method. For the coordinates to be displayed on top of the form, the boundaries of the Rectangle have to be established:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
          public class MainForm : System.Windows.Forms.Form
{
    private System.ComponentModel.Container components;
                         public MainForm()
    {
        Top = 100;
        Left = 75;
        Height = 100;
        Width = 500;

        MessageBox.Show(Bounds.ToString(), "Current rect");

        // Add delegates for mouse & keyboard events.
        this.MouseUp += new MouseEventHandler(OnMouseUp);
        this.MouseMove += new MouseEventHandler(OnMouseMove);
        this.KeyUp += new KeyEventHandler(OnKeyUp);
        InitializeComponent();
        CenterToScreen();
    }

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
        MessageBox.Show("Disposing this Form");
    }

    private void InitializeComponent()
    {
        this.components = new System.ComponentModel.Container();
        this.Size = new System.Drawing.Size(300,300);
        this.Text = "Form1";
    }

    [STAThread]
    static void Main() 
    {
        Application.Run(new MainForm());
    }

    protected void OnMouseUp(object sender, MouseEventArgs e)
    {
        // Which mouse button was clicked?
        if(e.Button == MouseButtons.Left)
            MessageBox.Show("Left click!");
        else if(e.Button == MouseButtons.Right)
            MessageBox.Show("Right click!");
        else if(e.Button == MouseButtons.Middle)
            MessageBox.Show("Middle click!");
    }

    protected void OnMouseMove(object sender, MouseEventArgs e)
    {
        this.Text = "Current Pos: (" + e.X + ", " + e.Y + ")";
    }

    public void OnKeyUp(object sender, KeyEventArgs e)
    {
        MessageBox.Show(e.KeyCode.ToString(), "Key Pressed!");
    }
}

Run this code on the command line. Type ‘type con > ControlApp.cs’, then paste the code to the console, and then press Ctrl-Z. Use the /target:winexe flag and provide a /reference:System.dll. Once the code is run, we are now in a position to see how we can use graphics. More to the point, we can now see how graphical applications can respond to standard input. For example, assume that you have to render some image outside of the scope of a standard Paint event handler – that you want to draw a small circle at the (x, y) position where the mouse was clicked. The first thing to do is to obtain a valid Graphics object, which can be obtained using the static Graphics.FromHwnd( ) method. Notice that we are passing our current handle as the sole parameter, and note that the Handle property is inherited from the Control class. This should make sense, but wouldn’t it work better with the MouseUp logic and add a new point to an internal collection of Point objects, followed by a call to Invalidate( ) (close the window and erase the point that was drawn):

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

public class MainForm : System.Windows.Forms.Form
{
    
    private ArrayList myPts = new ArrayList();

    public MainForm()
    {
        
        InitializeComponent();
        CenterToScreen();
        this.Text = "Basic Paint Form (click on me)";
    }

    public void MyPaintHandler(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;
    }

    private void InitializeComponent()
    {
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
        this.ClientSize = new System.Drawing.Size(292, 273);
        this.Text = "Form1";
        this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.MainForm_MouseDown);
        this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
    }

    [STAThread]
    static void Main() 
    {
        Application.Run(new MainForm());
    }

    private void MainForm_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        // Grab a new Graphics object.
        Graphics g = Graphics.FromHwnd(this.Handle);

        // Now draw a 10*10 circle at mouse click.
        // g.DrawEllipse(new Pen(Color.Green), e.X, e.Y, 10, 10);
                              // Add to points collection.
        myPts.Add(new Point(e.X, e.Y));
        Invalidate(); // means close window
    }

    private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        g.DrawString("Hello GDI+", new Font("Times New Roman", 20), 
        new SolidBrush(Color.Black), 0, 0);
                                        foreach(Point p in myPts)
        g.DrawEllipse(new Pen(Color.Green), p.X, p.Y, 10, 10);
    }
}

7.JPG

The circles shown are the result of the mouse clicks on those specified locations. So with that, assume that we have three JPEG images. We want to click one and have it identified on the top of the form. Consider this code:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

public class Form1 : System.Windows.Forms.Form
{
    private System.ComponentModel.Container components;

    // The images.
    private Image bMapImageA;
    private Image bMapImageB;
    private Image bMapImageC;

    // Rects for the images.
    private Rectangle rectA = new Rectangle(10, 10, 90, 90);
    private Rectangle rectB = new Rectangle(10, 110, 90, 90);
    private Rectangle rectC = new Rectangle(10, 210, 90, 90);

    // A polygon region.
    GraphicsPath myPath = new GraphicsPath();

    // Did they click on an image?
    private bool isImageClicked = false;
    private int imageClicked;

    public Form1()
    {
        InitializeComponent();

        // Fill the images with bitmaps.
        bMapImageA = new Bitmap(@"c:\users\dave\desktop\imageA.jpg");
        bMapImageB = new Bitmap(@"c:\users\dave\desktop\imageB.jpg");
        bMapImageC = new Bitmap(@"c:\users\dave\desktop\imageC.jpg");

        // Create an interesting region.
        myPath.StartFigure();
        myPath.AddLine(new Point(150, 10), new Point(120, 150));
        myPath.AddArc(200, 200, 100, 100, 0, 90);
        Point point1 = new Point(250, 250);
        Point point2 = new Point(350, 275);
        Point point3 = new Point(350, 325);
        Point point4 = new Point(250, 350);
        Point[] points = {point1, point2, point3, point4};
        myPath.AddCurve(points);
        myPath.CloseFigure();
        CenterToScreen();
    }

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }

    private void InitializeComponent()
    {
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 11);
        this.ClientSize = new System.Drawing.Size(292, 273);
        this.Text = "Form1";
        this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseUp);
        this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);
    }

    [STAThread]
    static void Main() 
    {
        Application.Run(new Form1());
    }

    private void Form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        // Get (x, y) of mouse click.
        Point mousePt = new Point(e.X, e.Y);

        // See if the mouse is anywhere in the 3 regions...
        if(rectA.Contains(mousePt))
        {
            isImageClicked = true;
            imageClicked = 0;
            this.Text = "You clicked image A";
        }
        else if(rectB.Contains(mousePt))
        {
            isImageClicked = true;
            imageClicked = 1;
            this.Text = "You clicked image B";
        }
        else if(rectC.Contains(mousePt))
        {
            isImageClicked = true;
            imageClicked = 2;
            this.Text = "You clicked image C";
        }
        else if(myPath.IsVisible(mousePt))
        {
            isImageClicked = true;
            imageClicked = 3;
            this.Text = "You clicked the strange shape...";
        }
        else    // Not in any shape, set defaults.
        {
            isImageClicked = false;
            this.Text = "Images";
        }
            
        // Redraw the client area.
        Invalidate();
    }

    private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
        Graphics g = e.Graphics;

        // Render all three images.
        g.DrawImage(bMapImageA, rectA);
        g.DrawImage(bMapImageB, rectB);
        g.DrawImage(bMapImageC, rectC);

        // Draw the graphics path.
        g.FillPath(Brushes.AliceBlue, myPath);

        // Draw outline (if clicked...)
        if(isImageClicked == true)
        {
            Pen outline = new Pen(Color.Red, 5);

            switch(imageClicked)
            {
                case 0:
                    g.DrawRectangle(outline, rectA);
                    break;
                
                case 1:
                    g.DrawRectangle(outline, rectB);
                    break;
                
                case 2:
                    g.DrawRectangle(outline, rectC);
                    break;

                case 3:
                    g.DrawPath(outline, myPath);
                    break;

                default:
                    break;
            }
        }
    }
}

So, what do we get?

8.JPG

Click the image, and have it identified on the top of the form. Not a lot of functionality, but definitely can be expanded on. Look back at the code and notice the array of Point structures, as well as the file paths for the images.

Animation and Double Buffering

To create an animation by displaying different frames at the rate of several dozen times per second, use an instance of the System.Windows.Forms.Timer class which will take care of triggering a regular call to a method by the thread of the window. The following code shows how to create an animation representing a square which rotates in the center of a window:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

public partial class AnimForm : Form {
    private float angle;
    private Timer timer = new Timer();
    private BufferedGraphics bufferedGraphics;

    public AnimForm() {
        BufferedGraphicsContext context = BufferedGraphicsManager.Current;
        context.MaximumBuffer = new Size( this.Width + 1, this.Height + 1 );
        bufferedGraphics = context.Allocate( this.CreateGraphics(),
        new Rectangle( 0, 0, this.Width, this.Height) );
        timer.Enabled = true;
        timer.Tick += OnTimer;
        timer.Interval = 20; // 50 images per second.
        timer.Start();
    }

    private void OnTimer( object sender, System.EventArgs e ) {
        angle ++;
        if (angle > 359)
            angle = 0;
        Graphics g = bufferedGraphics.Graphics;
        g.Clear( Color.Black );
        Matrix matrix = new Matrix();
        matrix.Rotate( angle, MatrixOrder.Append );
        matrix.Translate( this.ClientSize.Width / 2, 
            this.ClientSize.Height/ 2, MatrixOrder.Append );
        g.Transform = matrix;
        g.FillRectangle( Brushes.Azure, -100, -100, 200, 200 );
        bufferedGraphics.Render( Graphics.FromHwnd( this.Handle ) );
    }

    [System.STAThread]
    public static void Main() {
        Application.Run( new AnimForm() );
    }
}

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