Drawing on control canvas

Note: This article is relevant only to Windows form applications.

In .NET each control is composed of a canvas on which rendering is performed. To use the canvas of a control one needs to add an event handler to the Paint event. This article provides a couple of examples how to use the Paint event. These examples extend the rendering of common controls during runtime.

Scenario 1:

Let’s enhance a label to display an ellipse around the contents. First we need to bind an event handler to the label’s Paint event.

  1. label1.Paint += new PaintEventHandler(label1_Paint);

The signature for the Paint event handler is defined as:

  1. private void label1_Paint(object sender, PaintEventArgs e)
  2. {
  3. }

The next step is to draw an ellipse that takes it dimensions from the label.

  1. // Get the canvas on which the ellipse will be drawn
  2. Graphics canvas = e.Graphics;
  3. // Draw the ellipse
  4. canvas.DrawEllipse(Pens.Blue, e.ClipRectangle.X, e.ClipRectangle.Y, e.ClipRectangle.Width -2, e.ClipRectangle.Height - 2);

The purpose of the two statement above in the Paint event handler is to surround the label’s text within a blue ellipse.

Drawing on control - Figure 1

Note: The label AutoSize property was switched off during this example in order to allow enough space for proper rendering of the ellipse. Also the Windows form components define the clip rectangle a little bigger than the visible canvas. The difference in between the clip rectangle and the actual visible size is the placeholder for the control border. Therefore to have a proper rendering of the ellipse the width and height coordinates require the removal of 2 pixels from the rectangle returned by the ClipRectange property.

Scenario 2:

The label control directly supports the Underlining of text. However, let’s assume that a special underline effect is required. Say, we need to underline the text with a series of triangles next to each other.

  1. Graphics canvas = e.Graphics;
  2.  
  3. // Determine the number of points required. The divisor number, 3, is determine by
  4. // the number of edges for each triangle
  5. Point[] points = new Point[e.ClipRectangle.Width / 3];
  6.  
  7. for (int i = 0; i < points.Length; i += 2)
  8. {
  9.     // Draw the start point
  10.     points[i] = new Point((i * 3), e.ClipRectangle.Height - 2);
  11.     // Draw the middle point
  12.     points[i + 1] = new Point((i * 3) + 3, e.ClipRectangle.Height - 5);
  13.     // The end point is set by the next triangle start point
  14. }
  15.  
  16. // Draw the triangles
  17. canvas.DrawLines(new Pen(label1.ForeColor), points);

Drawing on control - Figure 2

Note: Like in the previous example, the label AutoSize property was switched off to allow enough space for proper rendering of the triangles.

Scenario 3:

The last example in this article shows how to display text on a semi-elliptical path.

  1. Graphics canvas = e.Graphics;
  2.  
  3. // The text that will be displayed
  4. string displayText = "Writing on a path";
  5.  
  6. // Calculate the ellipse angle from the x-axis
  7. double beta = (Math.PI / 180);
  8. double sinbeta = Math.Sin(beta);
  9. double cosbeta = Math.Cos(beta);
  10.  
  11. // Get the x and y radius
  12. double radiusX = -e.ClipRectangle.Width * 0.45;
  13. double radiusY = e.ClipRectangle.Height * 0.8;
  14. double step = 180.0 / displayText.Length;
  15.  
  16. for (int i = 0; i < displayText.Length; i++)
  17. {
  18.     // Calculate the angle tangent
  19.     double alpha = (i * step) * (-Math.PI / 180);
  20.     double sinalpha = Math.Sin(alpha);
  21.     double cosalpha = Math.Cos(alpha);
  22.  
  23.     // Move the canvas position onto a point of the ellipse path
  24.     canvas.TranslateTransform(
  25.         Convert.ToSingle(((e.ClipRectangle.Width - 5) / 2) +
  26.             (radiusX * cosalpha * cosbeta - radiusY * sinalpha * sinbeta)),
  27.         Convert.ToSingle((e.ClipRectangle.Height - 10) +
  28.             (radiusX * cosalpha * sinbeta + radiusY * sinalpha * cosbeta)));
  29.  
  30.     // Write the character
  31.     canvas.DrawString(displayText[i].ToString(), this.Font, Brushes.Black, 0, 0);
  32.  
  33.     // Reset all transformation
  34.     canvas.ResetTransform();
  35. }

Drawing on control - Figure 3

The example above uses some rendering techniques that have been placed to obtain the curved up effect displayed in the image above.

  1. The radius is measured in negative. The reason for this is to reverse the direction in which the text runs over the elliptical path.
    double radiusX = -e.ClipRectangle.Width * 0.45;
  2. The radii are a percentage of the canvas. To have the centre of path in the middle of the canvas the x radius needs to be half or less of the canvas width. While as the arc needs to cover the entire height of the canvas, the ratio was put slightly lower that full to make sure that no text does outside the drawing area.
    double radiusX = -e.ClipRectangle.Width * 0.45;
         double radiusY = e.ClipRectangle.Height * 0.8;
  3. The alpha angle is measured in negative. This is also related to the modifying the direction in which the ellipse points are calulated.

References