Windows Forms C# - Drawing and GDI+

2018/02/03 05:08
The System.Drawing and GDI + package
The System.Drawing package provides access to GDI + Windows features:
  • Drawing surfaces
  • Working with graphics and graphical transformations
  • Drawing geometric shapes
  • Working with images
  • Working with text and fonts
  • Printing on printer

It consists of several spaces:
  • System.Drawing - provides basic classes such as surfaces, pencils, brushes, classes for text painting.
  • System.Drawing.Imaging - Provides classes for working with images, pictures and icons, classes for converting to different file formats and for resizing images.
  • System.Drawing.Drawing2D - Provides classes for graphical transformations - blends, matrixes, and more.
  • System.Drawing.Text - Provides classes for accessing the graphical environment fonts.
  • System.Drawing.Printeing - Provides printer printing classes and system dialog boxes for printing.

Class Graphics
The System.Drawing.Graphics class provides an abstract drawing surface. Such a surface may be part of the screen control as well as part of a printer page or other device.
The most common drawing is done in the Paint event handler. If necessary, the graphical appearance of the control is redefined. The provided PaintEventArgs parameter contains the Graphics Object. A graphic object can be created using Control.CreateGraphics(). It must necessarily be released through the finally block or using construction because it is a valuable resource.

Coordinate system
The value for x is the location of the point along the x-axis, if x = 0 this means that the point is at the extreme left of the coordinate system.
The value for y is the location of the point on the y-axis, if y = 0 this means that the point is at the top of the coordinate system.

Example:
A starting point and an end point must be available to draw a line. The starting point is set by the values x1 and y1. The endpoint is set by the values of x2 and y2. In this case x1 = y1 = 75 pixels and x2 = y2 = 275 pixels.

To draw figures in C #, a starting point, an end point, and a Pen control must be available.
Pen control in C# Windows Forms
The most important properties of this control are:
  • DashStyle - sets the type of line. 
  • Color - sets the color of the line. 
  • Width - sets the line width (in pixels). 
  • Alignment - sets where to position Pen control over the theoretical line. 
C# has the ability to draw text as well. The Font and SolidBrush controls are required to draw text.
The main parameters of the Font Class are:
  • FontFamily - Sets the font, for example: "Courier New". 
  • Font size - sets the font size. 
  • FontStyle - sets the font style, for example: bold. 
The main parameters of the SolidBrush class is:
  • Color - sets the color of the brush. 
Sample tasks

1) Crete application that draws: circle, square, arch, pie, smiley man, figure by set values and text. Draw in two variants: the first one only with Pen controls and the second one with Pen and Brush controls (where possible). The field should be scaled to 6 parts. Click on the form to generate random colors and numbers and plot the shape.
Solution:
Create new windows Forms Application project and design the form as shown:


Name the controls as follows:
  • Radio button „Use only the PEN tool“ – rbPen. 
  • Radio button „Use both the PEN & BRUSH tools” – rbPenBrush. 
  • Button „Circle“ – bCircle. 
  • Button „Rectangle“ – bRectangle. 
  • Button „Arc and Pie“ – bArcPie. 
  • Button „Smiley Face“ – bSmilyFace. 
  • Button „Array of Points“ – bArrayOfPoints. 
  • Button „Strings“ – bStrings. 
  • Panel – pDrawingArea. 
The shapes will be drawn in the panel. The panel will be divided into 6 equal squares by drawing lines at the beginning of the program. There will be two radio button that will allow the user to choose whether to draw figures with or without fill. On button click it will be checked which of the radio buttons is selected. The bool variables CirclePen, RectanglePen, ArcPiePen, SmileyFacePen, ArrayOfPointsPen, StringsPen, will be set to true if radio button “Use only the PEN tool” is selected, and to false if radio button “Use both the PEN & BRUSH tools” is selected. For every button there is bool variable that will be set to true if the button is clicked. At the end the Refresh() property of the panel will be called. On panel refresh there will be check which button is clicked and the corresponding shapes will be drawn.


Declare all necessary variables as global.
This variables are necessary for setting the style and type of the lines and the fill of the brushes:
Pen myPen = new Pen(Color.Cyan, 3);
Pen myPen2 = new Pen(Color.Cyan, 2);

SolidBrush myBrush = new SolidBrush(Color.Black);

Font drawFont = new Font("Courier New", 10, FontStyle.Bold);
SolidBrush myBrushDrawText = new SolidBrush(Color.Cyan);
string drawString = "";

Graphics graphic;
Rectangle rectangle = new Rectangle();
PaintEventArgs pea;

Positioning variables. They will have two common variables xOrgin and yOrgin. This two variables will serve for general positioning of the shapes.
int xOrgin = 0;
int yOrgin = 0;
int x1 = 0;
int x2 = 0;
int y1 = 0;
int y2 = 0;
int width = 0;
int height = 0;
int startAngle = 0;
int sweepAngle = 0;
int[] polyArrayX;
int[] polyArrayY;
int num = 0;
int xMCoords = 0;
int yMCoords = 0;
The boolean variables that will serve for checking witch button is clicked and whish type of drawing is selected.
bool bCircleClicked = false;
bool bRectangleClicked = false;
bool bArcPieClicked = false;
bool bSmilyFaceClicked = false;
bool bArrayOfPointsClicked = false;
bool bStringsClicked = false;

bool CirclePen = false;
bool RectanglePen = false;
bool ArcPiePen = false;
bool SmilyFacePen = false;
bool ArrayOfPointsPen = false;
bool StringsPen = false;

In the Form1_Load event are initialized the main drawing components and the default selected radio button.
private void Form1_Load(object sender, EventArgs e)
{
   myPen2.Alignment = PenAlignment.Outset;
   graphic = this.CreateGraphics();
   pea = new PaintEventArgs(graphic, rectangle);

   this.Refresh();
   
   rbPen.Checked = true;
   rbPenBrush.Checked = false;
}

Separate universal function is created for polygon drawing.
private void DrawPolygon(int[] polyArrayX, int[] polyArrayY, 
              int polyPointsNum, Pen pen, PaintEventArgs e)
{
   Point[] pts = new Point[polyPointsNum];

   for (int b = 0; b < polyPointsNum; b++)
   {
      pts[b] = new Point(polyArrayX[b], polyArrayY[b]);
   }

   e.Graphics.DrawPolygon(pen, pts);
}

In the Paint event of the panel contains the grid and the checks for that which button is clicked.
private void pDrawingArea_Paint(object sender, PaintEventArgs e)
{
   e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
   
   xOrgin = 0;
   yOrgin = 0;

   // Drawing Grid
   x1 = xOrgin + 200;
   y1 = yOrgin + 0;
   x2 = xOrgin + 200;
   y2 = yOrgin + 400;
   e.Graphics.DrawLine(myPen, x1, y1, x2, y2);

   x1 = xOrgin + 400;
   y1 = yOrgin + 0;
   x2 = xOrgin + 400;
   y2 = yOrgin + 400;
   e.Graphics.DrawLine(myPen, x1, y1, x2, y2);

   x1 = xOrgin + 0;
   y1 = yOrgin + 200;
   x2 = xOrgin + 600;
   y2 = yOrgin + 200;
   e.Graphics.DrawLine(myPen, x1, y1, x2, y2);

   if (bCircleClicked)
      DrawingCircle(e);

   if (bRectangleClicked)
      DrawingRectangle(e);

   if (bArcPieClicked)
      DrawingArcPie(e);

   if (bSmilyFaceClicked)
      DrawingSmilyFace(e);

   if (bArrayOfPointsClicked)
      DrawingPolygon(e);

   if (bStringsClicked)
      DrawingStrings(e);
}

The functions for drawing the different shapes:
Circle
To draw a circle initial point must be set, by the variables x1 and y1, width and height for size of the circle, and a Pen control.
private void DrawingCircle(PaintEventArgs e)
{
    xOrgin = 0;
    yOrgin = 0;
    x1 = xOrgin + 40;
    y1 = yOrgin + 40;
    width = 80;
    height = 80;

    if (CirclePen == true)
        e.Graphics.DrawEllipse(myPen2, x1, y1, width, height);
    else
    {
        e.Graphics.DrawEllipse(myPen2, x1, y1, width, height);
        e.Graphics.FillEllipse(myBrush, x1, y1, width, height);
    }
}

Square
To draw a square initial point must be set, by the variables x1 and y1, width and height for size of the circle, and a Pen control.
private void DrawingRectangle(PaintEventArgs e)
{
    xOrgin = 200;
    yOrgin = 0;
    x1 = xOrgin + 40;
    y1 = yOrgin + 40;
    width = 80;
    height = 80;

    if (RectanglePen == true)
        e.Graphics.DrawRectangle(myPen2, x1, y1, width, height);
    else
    {
        e.Graphics.DrawRectangle(myPen2, x1, y1, width, height);
        e.Graphics.FillRectangle(myBrush, x1, y1, width, height);
    }
}

Arc and Pie
To draw this shapes initial point must be set, width and height, start and end angles and a Pen control are necessary.
private void DrawingArcPie(PaintEventArgs e)
{
    xOrgin = 400;
    yOrgin = 0;
    x1 = xOrgin + 40;
    y1 = yOrgin + 40;
    width = 60;
    height = 60;
    x2 = x1 + width + 20;
    y2 = y1;
    startAngle = 0;
    sweepAngle = 90;

    if (ArcPiePen == true)
    {
        e.Graphics.DrawArc(myPen2, x1, y1, width, height, startAngle, sweepAngle);
        e.Graphics.DrawPie(myPen2, x2, y2, width, height, startAngle, sweepAngle);
    }
    else
    {
        e.Graphics.DrawArc(myPen2, x1, y1, width, height, startAngle, sweepAngle);
        e.Graphics.DrawPie(myPen2, x2, y2, width, height, startAngle, sweepAngle);
        e.Graphics.FillPie(myBrush, x2, y2, width, height, startAngle, sweepAngle);
    }
}

Smiley Face
It drawn with the help of above shapes.
private void DrawingSmilyFace(PaintEventArgs e)
{
    xOrgin = 0;
    yOrgin = 200;
    x1 = xOrgin + 40;
    y1 = yOrgin + 40;
    width = 40;
    height = 40;

    if (SmilyFacePen == true)
    {
        e.Graphics.DrawEllipse(myPen2, x1, y1, width, height);

        x2 = x1 + 8;
        y2 = y1 + 15;
        width = 24;
        height = 20;
        startAngle = 0;
        sweepAngle = 180;

        e.Graphics.DrawArc(myPen2, x2, y2, width, height, startAngle, sweepAngle);

        x1 = x1 + 8;
        y1 = y1 + 10;
        width = 8;
        height = 8;

        e.Graphics.DrawEllipse(myPen2, x1, y1, width, height);

        x1 = x1 + 16;

        e.Graphics.DrawEllipse(myPen2, x1, y1, width, height);
    }
    else
    {
        Pen pen = new Pen(Color.Red, 2);
        SolidBrush solidBrush = new SolidBrush(Color.Yellow);

        e.Graphics.DrawEllipse(myPen2, x1, y1, width, height);
        e.Graphics.FillEllipse(solidBrush, x1, y1, width, height);

        x2 = x1 + 8;
        y2 = y1 + 15;
        width = 24;
        height = 20;
        startAngle = 0;
        sweepAngle = 180;
                
        e.Graphics.DrawArc(pen, x2, y2, width, height, startAngle, sweepAngle);

        x1 = x1 + 8;
        y1 = y1 + 10;
        width = 8;
        height = 8;

        e.Graphics.DrawEllipse(myPen2, x1, y1, width, height);
        solidBrush.Color = Color.Blue;
        e.Graphics.FillEllipse(solidBrush, x1, y1, width, height);

        x1 = x1 + 16;

        e.Graphics.DrawEllipse(myPen2, x1, y1, width, height);
        e.Graphics.FillEllipse(solidBrush, x1, y1, width, height);
    }
}

Shapes drawn by predefined points
To draw polygon a Pen and array of points must be set.
To draw bezier curves a Pen and a sequence of 4 points must be set.

private void DrawingPolygon(PaintEventArgs e)
{
    int px1, px2, px3;
    int py1, py2, py3;
            
    Point pt1, pt2, pt3, pt4;
    int x, y;

    xOrgin = 200;
    yOrgin = 200;

    px1 = xOrgin + 40;
    py1 = yOrgin + 40;
    px2 = px1 + 80;
    py2 = py1;
    px3 = px1 + 40;
    py3 = py1 + 45;

    const int pPointsNum = 3;
    polyArrayX = new int[pPointsNum] { px1, px2, px3 };
    polyArrayY = new int[pPointsNum] { py1, py2, py3 };

    x = 300;
    y = 350;
    pt1 = new Point(x - 30, y - 30);
    pt2 = new Point(x - 130, y + 130);
    pt3 = new Point(x + 130, y - 130);
    pt4 = new Point(x + 30, y + 30);

    if (ArrayOfPointsPen == true)
    {
        DrawPolygon(polyArrayX, polyArrayY, pPointsNum, myPen2, e);
        e.Graphics.DrawBezier(myPen2, pt1, pt2, pt3, pt4);
    }
    else
    {
        x1 = xOrgin + 40;
        y1 = yOrgin + 40;

        drawString = "No, no, no \n\tBambino \n\t\t:(((";
        e.Graphics.DrawString(drawString, drawFont, myBrushDrawText, x1, y1);
    }
}

Text
To draw text the following must be set: the desired text, font, solid brush and start point.
private void DrawingStrings(PaintEventArgs e)
{
    xOrgin = 400;
    yOrgin = 200;
    x1 = xOrgin + 40;
    y1 = yOrgin + 40;
    width = 50;
    height = 50;
    drawString = "Hello \n\tWorld \n\t\t:)))";

    if (StringsPen == true)
        e.Graphics.DrawString(drawString, drawFont, myBrushDrawText, x1, y1);
    else
    {
        drawString = "Bye Bye \n\tWorld \n\t\t:(((";
        e.Graphics.DrawString(drawString, drawFont, myBrushDrawText, x1, y1);
    }
}

Here are the click events for the buttons:
private void bCircle_Click(object sender, EventArgs e)
{
    if (rbPenBrush.Checked == true) CirclePen = false;
    else CirclePen = true;

    bCircleClicked = true;
    pDrawingArea.Refresh();
}

private void bRectangle_Click(object sender, EventArgs e)
{
    if (rbPenBrush.Checked == true) RectanglePen = false;
    else RectanglePen = true;

    bRectangleClicked = true;
    pDrawingArea.Refresh();
}

private void bArcPie_Click(object sender, EventArgs e)
{
    if (rbPenBrush.Checked == true) ArcPiePen = false;
    else ArcPiePen = true;

    bArcPieClicked = true;
    pDrawingArea.Refresh();
}

private void bSmilyFace_Click(object sender, EventArgs e)
{
    if (rbPenBrush.Checked == true) SmilyFacePen = false;
    else SmilyFacePen = true;

    bSmilyFaceClicked = true;
    pDrawingArea.Refresh();
}

private void bArrayOfPoints_Click(object sender, EventArgs e)
{
    if (rbPenBrush.Checked == true) ArrayOfPointsPen = false;
    else ArrayOfPointsPen = true;

    bArrayOfPointsClicked = true;
    pDrawingArea.Refresh();
}

private void bStrings_Click(object sender, EventArgs e)
{
    if (rbPenBrush.Checked == true) StringsPen = false;
    else StringsPen = true;

    bStringsClicked = true;
    pDrawingArea.Refresh();
}

Generating color circles with random numbers will be separate function. The function will generate random numbers for each color component (R, G, B). There will be two brushes. One for the color of the circle and one for the contrast color of the text in the circle. 
private void DrawingNumDotsRndColor(int xCoords, int yCoords, PaintEventArgs e)
{
    int red, green, blue;

    Random rnd = new Random();
    red = rnd.Next(256);
    green = rnd.Next(256);
    blue = rnd.Next(256);

    SolidBrush myBrushNumDot = new SolidBrush(Color.FromArgb(255, red, green, blue));
    SolidBrush myBrushNumDot2 = new SolidBrush(Color.FromArgb(255, 255 - red, 255 - green, 255 - blue));

    num++;

    x1 = xMCoords;
    y1 = yMCoords;
    width = 20;
    height = 20;
    e.Graphics.FillEllipse(myBrushNumDot, x1, y1, width, height);
    e.Graphics.DrawString(num.ToString(), drawFont, myBrushNumDot2, x1, y1);
}

In the Form1_MouseDown event on mouse click the cursor coordinates will be taken and a random color circle with number will be generated by calling the above function.
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
    xMCoords = e.X;
    yMCoords = e.Y;

    DrawingNumDotsRndColor(xMCoords, yMCoords, pea);
}


Self-assignments
1) Draw grid of nine equal squares with size 60x60. Draw the following shapes:
  • Line – start point (10,10), end point (40,40); 
  • Circle – start point (10,70), width: 40, height: 40; 
  • Rectangle – start point (15, 130), width: 40, height: 30; 
  • Trapeze – start point (70,20), bases 20 and 40, height: 40; 
  • Sliced pizza – use the Pie, start point (90, 90); 
  • Diamond – start point (70, 150), width: 40, height, 40; 
  • Right-angled triangle – start point (130,10), catheters 40; 
  • Sinusoid – start point (150, 70) 

2) Draw coordinate axis and graphic by the following points: 
  • 0,0 
  • 90,20 
  • 160,35 
  • 240,50 
  • 320,80 
  • 360,120 
  • 375,160, 
  • 390,250 
  • 400,480