Beautify Your UIs with Perfect Pen and Brush Control

Beautify Your UIs with Perfect Pen and Brush Control

f you’ve done any Windows graphics programming at all, you know about Pens and Brushes. Pens determine the characteristics of lines, such as color and thickness. Brushes determine the characteristics of filled areas, notably the fill color.

Many programmers use the Pens class to get stock, one-pixel wide pens of various colors as in the code e.Graphics.DrawLine(Pens.Red, 10, 10, 100, 100).They also use the Brushes class to get stock solid colored brushes as in:

   e.Graphics.FillEllipse(Brushes.Beige, _       New Rectangle(10, 10, 50, 100))

But Pens and Brushes can do more?much more.

Line Joins
When you use the DrawLines or DrawPolygon methods to draw a series of connected lines, you can choose to connect the lines in one of the four ways shown in Figure 1. The thick orange lines show the join styles and thin black lines drawn on top show where the lines are actually drawn.

The Bevel style cuts the corners off at each turn so the bevel touches a point where two lines meet. The bevel’s angle is evenly spaced between the angles of the incoming and outgoing lines. If you think of the line as a laser beam, the bevel is like a mirror making it bounce from one line to the next.

Figure 1. Line Join Styles: Here’s the output of the sample program “LineJoins,” which demonstrates line join styles.

The Miter style extends the sides of the lines at a corner until they meet. For very sharp turns, that can make the corner stick pretty far out.

The MiterClipped style is the same as Miter unless a corner sticks out too far in which case it switches to Bevel.

Finally the Round style makes the corners rounded so the edge of the line is the same distance from the lines at all times.

To use a particular join style, you first create a Pen object and then set its LineJoin property as shown in the following code:

   ' Use an orange Pen, 10 pixels wide.   Using the_pen As New Pen(Color.Orange, 10)       the_pen.LineJoin = LineJoin.Round       e.Graphics.DrawLines(the_pen, pts)   End Using
Author’s Note: The new Using statement automatically calls a Pen’s or Brush’s Dispose method, so I often employ it to simplify the code when dealing with Pens and Brushes.

Most of the Pen techniques described in this article follow a similar pattern: you create a Pen and then modify the Pen’s properties.

Line Caps
A Pen’s StartCap and EndCap properties determine what the ends of lines look like. Figure 2 shows samples of the available styles.

Figure 2. StartCap and EndCap Properties: The sample program “PenCaps” shows various Pen StartCap and EndCap styles.

The Round cap styles go nicely with the Round LineJoin style. The Flat cap styles usually work well with the other LineJoin styles.

For example, the following code draws a line with a round ball at the start and an arrowhead at the end:

   Using the_pen As New Pen(Color.Blue, 10)       the_pen.StartCap = LineCap.RoundAnchor       the_pen.EndCap = LineCap.ArrowAnchor       e.Graphics.DrawLine(the_pen, 10, 10, 100, 100)   End Using

Dash Styles
The DashStyle property determines whether a Pen draws lines that are solid, dashed, dotted, or in some other style. Figure 3 shows samples of the five pre-defined dash styles plus one custom style at the bottom.

To use a standard dash style, simply set the Pen’s DashStyle property as in the following code.

Figure 3. Dash Styles: The sample program “DashStyles” shows standard dash styles and one custom style (at the bottom).
   Using the_pen As New Pen(Color.Red, 3)       the_pen.DashStyle = DashStyle.Dash       e.Graphics.DrawLine(the_pen, 10, 10, 100, 200)   End Using

To make a custom dash style, create an array of Singles that indicate the number of units that should be drawn and then skipped while drawing the line. The array can contain as many values as you like. For example, if the Singles array holds the values 5, 1, 5, 1, 1, 1, it means: draw 5 units, skip 1 unit, draw 5 more, skip 1, draw 1, and finally, skip 1 unit. The Pen repeats these values as needed to draw longer lines.

The units in this array are Pen widths; so if the Pen you’re using is 5 pixels wide then a 1 in the array means skip 5 pixels.

After you create the array, set the Pen’s DashStyle property to Custom and set its DashPattern property to the array. The following code draws a line with the pattern shown at the bottom of Figure 3:

   Using the_pen As New Pen(Color.Red, 3)       Dim dash_pattern() As Single = {5, 1, 5, 1, 1, 1}       the_pen.DashStyle = DashStyle.Custom       the_pen.DashPattern = dash_pattern       e.Graphics.DrawLine(the_pen, 10, 10, 100, 200)   End Using

Dash Caps

Figure 4. Dash Caps: The output of the sample program “DashCaps” illustrates how you can control the ends of dashed lines.

Just as you can specify how the Pen should draw its end points, you can similarly specify how it should draw the ends of dashes. Set the Pen’s DashStyle property to something other than Solid and then set its DashCap property to one of the three values shown in Figure 4.

For example, the following code draws a thick green line with the Triangle dash cap:

   Using the_pen As New Pen(Color.Green, 11)       the_pen.DashStyle = DashStyle.Dash       the_pen.DashCap = DashCap.Triangle       e.Graphics.DrawLine(the_pen, 10, 10, 100, 100)   End Using

Custom Line Caps
So far, modifying Pens has been fairly easy. You need to set only a property or two to use different line joins, end caps, dash styles, and dash caps. But custom end caps are a bit harder.

To make a custom end cap, make a GraphicsPath object and use its methods to add whatever drawing commands you like to the end cap?lines, ellipses, arcs, whatever. Next create a new CustomLineCap object, passing the GraphicsPath you built to its constructor. Finally set the Pen’s CustomStartCap or CustomEndCap property to the CustomLineCap object.

One complicating factor is that the GraphicsPath that defines the end caps uses a special coordinate system that is relative to the line’s orientation. This lets your end caps rotate as needed to match up with the line but it does make it a bit harder to figure out how to build the GraphicsPath object.

If you think of yourself as standing on the end of the line looking toward the line (so the line is in front of you), then the X coordinate increases to your right and the Y coordinate increases behind you. (This almost makes sense if you think of the line as running from the bottom of your screen to the top because on the screen X coordinates increase to the right and Y coordinates increase toward the bottom.)

It’s confusing enough that you may want to draw the result you want on a piece of paper and draw the coordinates on top to help figure out what parameters to pass into the GraphicsPath methods.

The sample program “CustomEndCaps” uses the following code to draw the line shown in Figure 5.

Figure 5. Drawing Custom End Caps: The sample program “CustomEndCaps” shows sample start and end caps.
   Using the_pen As New Pen(Color.Blue, 10)       ' Make the start cap.       Dim start_path As New GraphicsPath()       start_path.AddLine(0, 0, 2, 0)       start_path.AddLine(0, 0, 0, 4)       Dim start_line_cap As New CustomLineCap( _          Nothing, start_path)       the_pen.CustomStartCap = start_line_cap          ' Make the end cap.       Dim end_path As New GraphicsPath()       end_path.AddLine(0, 0, 2, -2)       end_path.AddLine(0, 0, -2, -2)       Dim end_line_cap As New CustomLineCap(Nothing, end_path)       the_pen.CustomEndCap = end_line_cap          ' Draw a sample.       Dim x1 As Integer = 50       Dim x2 As Integer = 250       Dim y As Integer = 50       e.Graphics.DrawLine(the_pen, x1, y, x2, y)   End Using

For the start cap, the code makes a GraphicsPath and adds a short line in the X direction and a longer line in the Y direction. You can see them on the left in Figure 5. Take a moment to visualize yourself standing on the end of the line (where the two smaller segments meet) and looking to the right along the line’s length. X increases to your right and Y increases behind you.

To draw its end cap, the program makes another GraphicsPath object and adds lines angling in the negative Y direction, which is back along the line’s length, and in both the positive and negative X directions. That makes the arrowhead on the right in Figure 5.

Compound Pens
Perhaps the least known feature of the Pen class is the ability to make compound Pens. A compound Pen draws lines that are striped lengthwise?sort of like dashes along the line’s width instead of its length.

To create a compound Pen, make an array of Singles with values between 0.0 and 1.0. The values indicate the fractions of the distance across the width of the line where drawing should start and stop.

For example, the values 0.0, 0.45, 0.55, 1.0 indicate that the line should be drawn from 0.0 to 0.45 of the width, not drawn from 0.45 to 0.55, and drawn from 0.55 to 1.0. The result is a line that has a thin empty stripe down its middle 10 percent.

Set the Pen’s CompoundArray property to this array and draw as usual. The following code draws an ellipse with a thick blue pen that has an undrawn stripe down its middle:

   Using blue_pen As New Pen(Color.Blue, 20)       Dim compound_array As Single() = {0.0, 0.45, 0.55, 1.0}       blue_pen.CompoundArray = compound_array       e.Graphics.DrawEllipse(blue_pen, _           New Rectangle(20, 20, 100, 100))   End Using

The program “CompoundPens” (see its output in Figure 6), uses several compound Pens. The outer blue ellipse uses compound array values 0.0, 0.1, 0.2, 0.8, 0.9, 1.0. The smaller green ellipse inside the blue one uses array values 0.0, 0.4, 0.5, 1.0 and has its DashStyle set to Dot. Notice the small gap on the left where two dots don’t quite meet each other.

The star in Figure 6 is drawn with two Pens. The first is red and uses the array values 0.0, 0.5, 1.0, 1.0; it draws only the first side of the line. The second Pen is orange and uses the values 0.0, 0.0, 0.5, 1.0 to draw the second side of the line.

Figure 6. Compound Pens: You can use compound pens to draw “striped” lines in which you control the width of the stripes.
Figure 7. Antialiasing: The ellipse on the right sets the Pen’s SmoothingMode property to AntiAlias, resulting in a noticeably smoother figure.

Smooth Lines
I want to mention one final Pen technique before moving on to Brushes. You can use a Graphics object’s SmoothingMode property to determine whether it uses anti-aliasing to make the edges of drawn lines smoother.

For example, the following code draws an ellipse. It then sets SmoothingMode to AntiAlias and draws another ellipse. As you can see in Figure 7, the second ellipse (on the right) is noticeably smoother.

   Using the_pen As New Pen(Color.Black, 10)       ' Draw a rough circle.       e.Graphics.DrawEllipse(the_pen, 20, 20, 100, 100)          ' Draw a smooth circle.       e.Graphics.SmoothingMode = SmoothingMode.AntiAlias       e.Graphics.DrawEllipse(the_pen, 20 + 120, 20, 100, 100)   End Using

Be aware that using the SmoothingMode.AntiAlias setting causes drawing to take slightly longer. For most applications the difference in speed is small,?but the difference in quality is huge, so it’s often worth using. (The Graphics object’s TextRenderingHint property works similar magic when you draw text, although the default these days seems to be to use anti-aliasing so you’re probably already doing that without even knowing about it.)

Solid Brushes
To draw lines with special Pen effects, you create a Pen object and then set its properties to change its start and end caps, dash style, dash caps, and other characteristics. In contrast, to fill shapes in different ways, you use different Brush classes.

The Brushes class provides a large assortment of stock solid-color Brushes such as Brushes.Orchid and Brushes.Honeydew. You can also use the SolidBrush class to create your own solid brushes using non-standard colors.

For example, the following code fills a rectangle with a stock Orchid brush. It then makes a solid brush that is a light translucent blue (the first parameter to FromArgb means the color has an opacity of only 128 on a scale of 0 to 255, making it translucent) and uses it to fill a rectangle that overlaps the first one.

Figure 8. Predefined Hatch Patterns: Here’s the output of the sample program “HatchBrushes,” showing samples of the 56 predefined hatch patterns.
   e.Graphics.FillRectangle(Brushes.Orchid, _      New Rectangle(25, 0, 50, 100))   Using the_brush As New SolidBrush( _      Color.FromArgb(128, 0, 128, 255))      e.Graphics.FillRectangle(the_brush, _         New Rectangle(0, 25, 100, 50))   End Using

Even the commons solid brushes are quite useful. With a little creativity and some transparency they can be a lot of fun, but the .NET Framework also provides an assortment of other Brush classes that produce some truly amazing effects.

Hatched Brushes
The HatchBrush class fills areas with one of 56 predefined patterns as shown in Figure 8.

Using a HatchBrush is easy. Just create a new HatchBrush object, passing to its constructor the pattern you want, a foreground color, and a background color. The following code makes a HatchBrush that fills are area with the DiagonalBrick pattern drawn in black over a yellow background.

Figure 9. Using Texture Brushes: The figure shows a rectangle filled with a TextureBrush using a custom “Diamond” image.
   Using the_brush As New HatchBrush( _    HatchStyle.DiagonalBrick, Color.Black, Color.Yellow)       Dim rect As New Rectangle(0, 0, 100, 50)       e.Graphics.FillRectangle(the_brush, rect)   End Using

Texture Brushes
You can’t create a customized HatchBrush but you can probably accomplish what you want with a TextureBrush. To create a TextureBrush, pass its constructor the image that it should use to fill an area. The image can contain whatever pattern you want. It can also contain as many colors as you like so it’s even more flexible than a HatchBrush, which supports only foreground and background colors.

The sample program “TextureBrushes,” shown in Figure 9, uses the following code to fill a rectangle. It makes a TextureBrush from the program’s image resource named Diamond and then fills a rectangle named rect with the brush.

   Using the_brush As New TextureBrush(My.Resources.Diamond)       e.Graphics.FillRectangle(the_brush, rect)   End Using

Linear Gradient Brushes

Figure 10. Linear Gradient Brushes: Here’s a screenshot from the sample “LinearGradientBrushes” program that fills a rectangle with colors that blend smoothly from blue to lime green.

The LinearGradientBrush class fills an area with smoothly blended colors. Figure 10 shows program LinearGradientBrushes displaying one of the simpler possible gradients shading from blue in the upper left corner to lime green in the lower right.

The LinearGradientBrush class exposes several constructors. The version used by the LinearGradientBrushes program takes two points and the colors that it should use at those points as parameters. The brush smoothly transitions the colors as it moves from one point to the other.

The following code shows how the program works. It first defines the points and colors, then uses them to create a LinearGradientBrush and fill the rectangle, using the two points as the rectangle’s upper left and lower right corners.

   Dim point1 As New Point(20, 20)   Dim point2 As New Point(250, 160)   Dim color1 As Color = Color.Blue   Dim color2 As Color = Color.Lime      Using the_brush As New _      LinearGradientBrush(point1, point2, color1, color2)      Dim rect As New Rectangle( _         point1.X, point1.Y, _         point2.X - point1.X + 1, _         point2.Y - point1.Y + 1)      e.Graphics.FillRectangle(the_brush, rect)   End Using

Another way to create a LinearGradientBrush is to pass its constructor a rectangle, two colors, and an angle. The brush fills the area with the first color in the upper left, shading towards the second color in the lower right, with colors blending along the indicated angle. The following code creates a brush that transitions from blue to red at a 45 degree angle.

Figure 11. Controlling Gradient Angles: The four images in this figure show how the sample program “LinearGradientBrush_Angles” controls the gradient angle by creating brushes using different angle values.
   Dim rect As New Rectangle(10, 10, 100, 100)   Using the_brush As New _      LinearGradientBrush(rect, Color.Blue, Color.Red, 45)      e.Graphics.FillRectangle(the_brush, rect)   End Using

Figure 11 shows four areas with gradients applied at 0, 30, 60, and 90 degree angles. The program uses an arrow (drawn using a Pen with EndCap = ArrowAnchor) showing the color gradient’s direction and displays the angle in text.

To make common gradients easier to build, the class provides another overloaded constructor that takes as parameters a rectangle, two colors, and a gradient mode that can be BackwardDiagonal, ForwardDiagonal, Horizontal, or Vertical. The following code uses a ForwardDiagonal brush and a blue to red gradient.

   Dim rect As New Rectangle(10, 10, 200, 100)   Using the_brush As _      New LinearGradientBrush(rect, Color.Blue, Color.Red, _         LinearGradientMode.ForwardDiagonal)      e.Graphics.FillRectangle(the_brush, rect)   End Using

Interpolation Colors
A LinearGradientBrush isn’t limited to two colors; it can also transition between a series of colors. For this kind of brush, start by creating a LinearGradientBrush. Next create a ColorBlend object and set its Colors property to an array containing the list of colors that you want to shade between.

Set the ColorBlend object’s Positions property to an array of Singles giving the positions between 0.0 and 1.0 where you want each color to reach its pure value.

Finally set the LinearGradientBrush’s InterpolationColors property to the ColorBlend object.

The following code shades a rectangle from red to lime to blue. The ColorBlend’s Positions array indicates that the left edge of the rectangle should be red, the middle should be lime, and the right edge should be blue.

Figure 12. Multiple-color Gradients: The sample program “InterpolationColors” fills these rectangles with many smoothly blended colors.
   rect = New Rectangle(10, 10, 300, 150)   Using the_brush As _    New LinearGradientBrush(rect, Color.Blue, Color.Red, _    LinearGradientMode.Horizontal)       ' Define a color blend.       Dim color_blend As New ColorBlend()       color_blend.Colors = New Color() _           {Color.Red, Color.Lime, Color.Blue}       color_blend.Positions = New Single() {0.0, 0.5, 1.0}       the_brush.InterpolationColors = color_blend          ' Draw.       e.Graphics.FillRectangle(the_brush, rect)   End Using

Figure 12 shows the output from the sample program “InterpolationColors,” which uses the preceding code to fill the top rectangle. The program then uses a different brush to fill the lower rectangle with colors blending through red, orange, yellow, lime, blue, indigo, and purple.

You can apply the same concepts to drawing text. The sample program “LinearGradientText” draws its text using a LinearGradientBrush that shades from red to lime to blue (see Figure 13).

Figure 13. Linear Gradient Applied to Text: The figure illustrates how you can use a LinearGradientBrush to draw text as well as figures.

Path Gradient Brushes
As if LinearGradientBrushes weren’t cool enough, you can also define a PathGradientBrush that smoothly shades colors from points along a path towards a “center” point.

To use a PathGradientBrush, create an array of Points that defines a polygon and pass the array into the PathGradientBrush’s constructor. Set the brush’s CenterColor property to the color that you want the brush to use for the “center” of the polygon. If you like, you can set the brush’s CenterPoint property to the location of this central point. Note that the CenterPoint doesn’t truly have to be anywhere near the polygon’s actual center. If you don’t set this property explicitly, the brush picks a point based on the polygon’s points.

Next set the brush’s SurroundColors property to an array of Colors that holds the colors that you want the brush to use at the polygon’s points. Finally, you call the FillPolygon method to apply the gradient to the specified polygon.

If you don’t include enough colors for every point, the brush reuses the last color as many times as needed to make up the difference. But unfortunately, if you include too many colors in the array (more colors than points in the polygon), calling the FillPolygon method throws an Exception.

The following code shows a simple example. The variable pts is an array of Points that defines a heptagon (a seven-sided polygon). The brush shades from white in the center to the surround colors red, orange, yellow, lime, blue, indigo, and purple around the edges.

   Using the_brush As New PathGradientBrush(pts)       the_brush.CenterColor = Color.White          Dim surround_colors() As Color = { _           Color.Red, _           Color.Orange, _           Color.Yellow, _           Color.Lime, _           Color.Blue, _           Color.Indigo, _           Color.Purple _       }       the_brush.SurroundColors = surround_colors          e.Graphics.FillPolygon(the_brush, pts)   End Using

You can find the code in the sample program “PathGradientBrushes.” Figure 14 shows the output.

Figure 14. Apply a Path Gradient to Polygons: The figure illustrates using a PathGradientBrush to fill a heptagon with a gradient.
Figure 15. Interesting Gradient Effects: This star was created by using a PathGradientBrush with a white center color and different colors for each point.
Figure 16. Interesting Gradient Effects: The sample program “PathGradientHollowStar” fills a self-intersecting star with a PathGradientBrush.

Using PathGradientBrushes, you can achieve all sorts of interesting effects. The sample program “PathGradientStar,” shown in Figure 15, uses a PathGradientBrush to draw a star. The star’s points have different colors, the “valleys” between the points are white, and the center color is white.

The sample “PathGradientHollowStar” program (see Figure 16), draws a self-intersecting polygon. The points of the star have different colors and the center point is again white.

Solid Pens and Brushes are fine for everyday reports and forms but it takes only a little extra work to use dashed lines, hatch patterns, and linear color gradients to improve the output. Custom line caps, compound lines, multiple linear gradients, and path gradients are a bit harder but, when tastefully done, can add a little extra spice to an otherwise humdrum form or splash screen. These effects are seldom used, so with only a little extra effort you can create interesting and unique graphical applications.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist