Windows Forms 2.0 Programming (Microsoft .NET Development Series)

Whereas the Brush classes are used to fill shapes, the Pen class is used to frame shapes. The interesting members are shown here:

namespace System.Drawing { sealed class Pen : IDisposable, ... { // Constructors public Pen(Brush brush); public Pen(Brush brush, float width); public Pen(Color color); public Pen(Color color, float width); // Properties public PenAlignment Alignment { get; set; } public Brush Brush { get; set; } public Color Color { get; set; } public float[] CompoundArray { get; set; } public CustomLineCap CustomEndCap { get; set; } public CustomLineCap CustomStartCap { get; set; } public DashCap DashCap { get; set; } public float DashOffset { get; set; } public float[] DashPattern { get; set; } public DashStyle DashStyle { get; set; } public LineCap EndCap { get; set; } public LineJoin LineJoin { get; set; } public float MiterLimit { get; set; } public PenType PenType { get; } public LineCap StartCap { get; set; } public float Width { get; set; } // Transformation members elided... // Methods public void SetLineCap(...); } }

Pens have several interesting properties, including a width, a color or a brush, start and end cap styles, and a dash pattern for the line itself. One note of interest is that the width of a pen is specified in the units of the underlying Graphics object being drawn on (more information about Graphics units is available in Chapter 7). However, no matter what the underlying units, a pen width of 0 always translates into a width of 1 physical unit on the underlying Graphic surface. This lets you specify the smallest visible pen width without worrying about the units of a particular surface.

Notice that the Pen class is sealed. This means that it can't be used as a base class for deriving further penlike functionality. Instead, each pen has a type that governs its behavior, as determined by the PenType enumeration from the System.Drawing.Drawing2D namespace:

namespace System.Drawing.Drawing2D { enum PenType { SolidColor = 0, // Created from a color or a SolidBrush HatchFill = 1, // Created from a HatchBrush TextureFill = 2, // Created from a TextureBrush PathGradient = 3, // Created from a PathGradientBrush LinearGradient = 4, // Created from a LinearGradientBrush } }

If you're interested in common, solid-color pens, the 141 named pens are provided as static Pen properties on the Pens class, and 33 system pens are provided as static Pen properties on the SystemPens class, providing the same usage as the corresponding Brushes and SystemBrushes classes. As with SystemBrushes, the FromSystemColor method of the SystemPens class returns a pen in one of the system colors that's managed by .NET.

Line Caps

In addition to their brushlike behavior, pens have behavior at their ends, at their joints, and along their length that brushes don't have. For example, each end can be capped in a different style, as determined by the LineCap enumeration shown in Figure 5.9.

Figure 5.9. Examples from the LineCap Enumeration

All these lines were generated with a black pen of width 12 passed to the Graphics. DrawLine method. We drew the white line of width 1 in the middle by using a separate call to Graphics.DrawLine to show the two end points that define the line. Each black pen is defined with the EndCap property set to a value from the LineCap enumeration:

using( Pen pen = new Pen(Color.Black, 12) ) { pen.EndCap = LineCap.Flat; // default g.DrawLine( pen, x, y + height * 2/3, x + width * 2/3, y + height * 2/3); g.DrawLine( whitePen, x, y + height * 2/3, x + width * 2/3, y + height * 2/3); ... }

The default line cap style is flat, which is what all the StartCap properties are set to. You'll notice some familiar line cap styles, including flat, round, square, and triangle, which have no anchor, as well as arrow, diamond, round, and square, which have anchors. An anchor indicates that part of the line cap extends beyond the width of the pen. The difference between square and flat, on the other hand, dictates whether the line cap extends beyond the end of the line (as square does, but flat does not).

You can manage these kinds of drawing behaviors independently by using the LineCap.Custom enumeration value and setting the CustomStartCap or CustomEndCap field to a class that derives from the CustomLineCap class (from the System.Drawing. Drawing2D namespace). The custom line cap in Figure 5.9 shows a pen created using an instance of the AdjustableArrowCap class, the only custom end cap class that .NET provides:

using( Pen pen = new Pen(Color.Black, 12) ) { pen.EndCap = LineCap.Custom; // width and height of 3 and unfilled arrowhead pen.CustomEndCap = new AdjustableArrowCap(3f, 3f, false); ... }

Dashes

In addition to the ends having special styles, a line can have a dash style, as defined by the DashStyle enumeration, shown in Figure 5.10.

Figure 5.10. Examples Using the DashStyle Enumeration

Each of the lines was created by setting the DashStyle property of the pen. The DashStyle.Custom value is used to set custom dash and space lengths, where each length is a multiplier of the width. For example, the following code draws the increasing-length dashes shown in Figure 5.10 with a constant space length:

using( Pen pen = new Pen(Color.Black, 12) ) { pen.DashStyle = DashStyle.Custom; // Set increasing dashes with constant spaces pen.DashPattern = new float[] { 1f, 1f, 2f, 1f, 3f, 1f, 4f, 1f }; g.DrawLine( pen, x + 10, y + height * 2/3, x + width - 20, y + height * 2/3); ... }

If you'd like to exercise more control over your custom dash settings, you can set the DashCap property on the pen to any of the values in the DashCap enumeration, which is a subset of the values in the LineCap enumeration with only Flat (the default), Round, and Triangle.

To exercise more control over the line itself, in addition to dash settings you can define compound pens using the CompoundArray property. This allows you to provide lines and spaces parallel to the lines being drawn instead of perpendicularly, as dash settings do. For example, Figure 5.11 was drawn with a pen set up this way:

Figure 5.11. A Single Rectangle Drawn with a Pen Using a Compound Array

using( Pen pen = new Pen(Color.Black, 20) ) { // Set percentages of width where line starts, then space starts, // then line starts again, etc., in alternating pattern pen.CompoundArray = new float[] { 0.0f, 0.25f, 0.45f, 0.55f, 0.75f, 1.0f, }; g.DrawRectangle(pen, new Rectangle(...)); }

Pen Alignment

Most of the examples, including Figure 5.11, show pens of width greater than 1. When you draw a line of width greater than 1, the question is, where do the extra pixels goabove the line being drawn, below it, or somewhere else? The default pen alignment is centered, which means that half the width goes inside the shape being drawn, and the other half goes outside. The alignment can also be inset, which means that the entire width of the pen is inside the shape being drawn, as shown in Figure 5.12.

Figure 5.12. Pen Alignment Options (See Plate 10)

In Figure 5.12, both ellipses are drawn using a rectangle of the same dimensions (as shown by the red line), but the different alignments determine where the width of the line is drawn. There are actually five values in the PenAlignment enumeration, but only Center and Inset are currently supported, and Inset is used only for closed shapes (an open figure has no "inside"). The other threeLeft, Outset, and Rightrender as if you had used Center.

Joins

One final consideration when you draw figures that have angles is what to do with the line at the angle. In Figure 5.13, the four values in the LineJoin enumeration have been set in the Pen class's LineJoin property before the rectangles were drawn (again, a white line of width 1 is used to show the shape being drawn).

Figure 5.13. Sample LineJoin Values

In Figure 5.13, each corner provides a different join. The one exception is MiterClipped, which changes between Bevel and Miter dynamically based on the limit set by the MiterLimit property. The length of a miter is the distance between the inner corner and the outer corner of a join, where the distance is a function of line thickness and the angle of the corner. When the ratio of the line thickness to the miter length exceeds the miter limit, the join is beveled; otherwise, it's mitered.

For example, consider the following combination of pen size and miter limit:

// MiterClipped Join using( Pen blackPen = new Pen(Color.Black, 10) ) { blackPen.LineJoin = LineJoin.MiterClipped; blackPen.MiterLimit = 5.0F; // Default is 10.0f // Draw four sets of angled lines of increasing angle size ... }

Figure 5.14 illustrates the results.

Figure 5.14. Effects of MiterLimit Property on Joins

As the corner angle decreases, the miter length increases to the point where its ratio to the line thickness tops the miter limit, resulting in the beveled corner that's applied to the top two sets of angled lines.

Creating Pens from Brushes

So far in this section on pens, all the examples have used solid-color pens. However, you can also create a pen from a brush and thereby employ any effect you can create using the multitude of brushes provided by System.Drawing, including textures, hatches, and gradients. For example, Figure 5.15 shows an image you first encountered in Chapter 1: Hello, Windows Forms.

Figure 5.15. Creating a Pen from a LinearGradientBrush (See Plate 2)

The pen used to draw the lines in Figure 5.15 was created from a LinearGradientBrush:

using( LinearGradientBrush brush = new LinearGradientBrush( this.ClientRectangle, Color.Empty, Color.Empty, 45) ) { ColorBlend blend = new ColorBlend(); blend.Colors = new Color[] { Color.Red, Color.Green, Color.Blue }; blend.Positions = new float[] { 0, .5f, 1 }; brush.InterpolationColors = blend; using( Pen pen = new Pen(brush) ) { ... } ... }

Категории