[This article is part of a series that starts in this article.]
My intention is to cover a full spectrum of activities around custom control development, with an emphasis on the compromises, workarounds, and special care that must be taken when developing controls for the Compact Framework. In my first article, I talked about how most design-time attributes must be applied to control classes and their members, and what some of those attributes mean. I have a number of articles planned that explore those attributes more, and will go into extending the design-time experience in more depth, but I’m going to take a detour into custom painting of the control surface first, so we have a control to reference and work with in the examples.
Polygon Clipping
If you’re new to creating graphical effects and unfamiliar with the techniques invovlved, clipping refers to the chopping off of an image based on some kind of border or boundary. In Windows Forms interfaces, controls are inherently rectangular because clipping occurs automatically at the window’s boundary (which is a shame considering how this presumption of need slows rendering, and WPF takes good advantage of not doing so). Everything outside the control’s outer shape doesn’t get drawn at all. You can draw anywhere you want, including negative coordinates, but only the points that fall within the clipping region will be displayed.
But what if you want to make your control a different shape, other than the standard rectangle, like an ellipse or a rounded rectangle? How do you make sure that whatever you draw inside never leaks outside of your defined shape? In the full .NET Framework, there is a Region property in the Control class that defines these boundaries, and there are several good articles that explain this. The clipping mask is applied based on that Region’s definition. In Compact Framework, the Region property doesn’t exist, and you’re forced to find your own way of defining different shapes.
The key to this is to understand the Graphics class’s Fill methods. While FillEllipse and FillRectangle definitely have their uses, I’d like to focus on situations that are a little bit more demanding, such as when you want to represent a more complex shape like a rounded rectangle (with many points) or some kind of UML diagram element. FillPolygon takes a list of Points, and with them can define the most eccentric and specific of shapes. By filling a polygon with an image using a TextureBrush, clipping happens automatically as part of the operation.
Let’s take a look at some code to see how we perform each of these steps: preparing and drawing on a bitmap image in memory, defining our shape’s boundaries, and then clipping that image within the specified shape.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Drawing;
using System.Reflection;
namespace CustomControlsDemo
{
public class ClippingControl : Control
{
protected override void OnPaint(PaintEventArgs e)
{
// define a canvas for the visual content of the control
Bitmap MyBitmap = new Bitmap(Width, Height);
// create a painting tools object
Graphics g = Graphics.FromImage(MyBitmap);
// draw graphics on our bitmap
g.FillRectangle(new SolidBrush(Color.PaleGoldenrod), ClientRectangle);
g.DrawLine(new Pen(Color.Black), 0, 0, Width - 1, Height - 1);
g.DrawEllipse(new Pen(Color.Black), 0, 0, Width - 1, Height - 1);
// dispose of the painting tools
g.Dispose();
// define the custom shape of the control: a trapezoid in this example
List<Point> Points = new List<Point>();
Points.AddRange(new Point[] { new Point(20, 0), new Point(Width - 21, 0),
new Point(Width - 1, Height - 1), new Point(0, Height - 1) });
// draw that content inside our defined shape, clipping everything that falls outside of the region;
// only if the image is much smaller than the control does it really get "tiled" and act like a textured painting brush
// but our bitmap image is the same size as the control, so we're just taking advantage of clipping
Brush ImageBrush = new TextureBrush(MyBitmap);
e.Graphics.FillPolygon(ImageBrush, Points.ToArray());
}
}
}
Although this control has a silly shape and doesn’t do much yet, it does illustrate the basics of painting within the bounds of an irregular shape. As long as we draw on MyBitmap, everything will be properly clipped by the call to FillPolygon. However, as you can see in the screenshots below, the white background around our custom shape could be a problem. You can change the BackColor property to match the color of the container its on (a Panel control in this case, which is Color.BurlyWood), but really it makes more sense for BackColor to describe the color within our shape. We’d like the surrounding background to blend in with whatever container the control is sitting in.
We can accomplish this with two simple changes. First, at some point before the FillPolygon call, we need to fill the entire control’s area with the BackColor property of the parent control. We will draw using the e.Graphics object, which paints on the whole rectangular control, not our g Graphics object, whose contents get clipped. Then, instead of hard coding Color.PaleGodenrod, we can use the BackColor property to specify our fill color. Here is the changed section of code:
// draw graphics on our bitmap
g.FillRectangle(new SolidBrush(BackColor), ClientRectangle);
g.DrawLine(new Pen(Color.Black), 0, 0, Width - 1, Height - 1);
g.DrawEllipse(new Pen(Color.Black), 0, 0, Width - 1, Height - 1);
// dispose of the painting tools
g.Dispose();
e.Graphics.FillRectangle(new SolidBrush(Parent.BackColor), ClientRectangle);
Now if we set the BackColor to PaleGodenrod, we’ll get this rendering:
Dragging the control off the panel and into the white area will cause the area around the control to paint white, so now you can see how it blends in with whatever background we have as long as it’s a solid color.
In a future article, after I’ve covered how to draw arcs and curves, I will revisit this technique and demonstrate how to draw rectangles with rounded corners.
[This article is part of a series that continues in this article.]