Collections of Shapes
It is often useful to collect various "building block" shapes into a single unit. Rather than managing each rectangle in a bar graph, for instance, it is often easier to group these objects into a single, manageable unit. If the objects need to be moved or redrawn, you can simply make one method call. Similarly, if you are transforming the objects, maybe rotating them all 45°, it is much easier to transform a group than transform each item independently. The System.Drawing.Drawing2D namespace provides us the Path class for grouping shapes.
Additionally, once you've defined your various object groups, it is often necessary to indicate how those groups interact with one another. If you've ever used a drawing application, you are undoubtedly familiar with the concepts of bring-to-front and send-to-back. These are features that allow an artist to indicate how shapes (or groups of shapes) relate to one another in layers. The System.Drawing namespace gives us the Region class for indicating object interaction and layers.
Paths
Paths enable more advanced drawing techniques in .NET. A path is made of one or more geometric shapes (rectangle, line, curve, and so on). By grouping shapes together in a path, we are able to manage and manipulate the group as one object. We add shapes to a path for storage in what is called the world coordinate space. This coordinate system is essentially virtual. It is the place where the shapes logically exist in memory relative to one another. The graphic can then be manipulated as a whole. It can be drawn to the screen over and over. In addition, it can be transformed (rotated, sheared, reflected, scaled) when moving from this logical world space to the physical device space (form). For example, you might have a 10 x 20 rectangle stored inside a path. When you place it on the form, you can rotate it 20° and sheer the rectangle. The key is that the rectangle still exists as a 10 x 20 rectangle (not rotated, not sheared) in the world space.
To create a path, we use the GraphicsPath class. This class provides methods like AddLine, AddRectangle, AddArc, and so on; each adds their shape to the path. Paths can contain multiple figures or groups of shapes that represent one object. When adding a shape to a path, it is best to indicate to which figure the shape belongs. We do this by calling the StartFigure method. Each subsequent call to an add function adds the shape to the figure. If we call StartFigure again, a new figure is started and all following shapes are added to the new figure. We call the CloseFigure method prior to starting a new figure if we wish the figure to be closed off, or connected from start point to end point.
Listing 9.6 creates a GraphicsPath instance. We add a few shapes to the GraphicsPath class and then display the path to the form.
Listing 9.6 GraphicsPath
Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click 'dimension variables of local scope Dim myGraphics As Graphics Dim myPen As New Pen(color:=Color.Blue, Width:=2) Dim myPath As System.Drawing.Drawing2D.GraphicsPath Dim myPoints(2) As Point 'create a new GraphicsPath instance with default values myPath = New System.Drawing.Drawing2D.GraphicsPath() 'start the figure myPath.StartFigure() 'add an ellipse for the head myPath.AddEllipse(x:=0, y:=0, Width:=50, Height:=70) 'add 2 ellipses to the eyes myPath.AddEllipse(x:=10, y:=10, Width:=10, Height:=8) myPath.AddEllipse(x:=30, y:=10, Width:=10, Height:=8) 'add bezier for the nose myPath.AddBezier( _ pt1:=New Point(x:=25, y:=30), _ pt2:=New Point(x:=15, y:=30), _ pt3:=New Point(x:=20, y:=40), _ pt4:=New Point(x:=25, y:=40)) 'add a points to make a curve for the mouth myPath.StartFigure() myPoints(0) = New Point(x:=10, y:=50) myPoints(1) = New Point(x:=25, y:=60) myPoints(2) = New Point(x:=40, y:=50) myPath.AddCurve(points:=myPoints) 'return the current form as a drawing surface myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle) 'output the Path to the drawing surface of the form myGraphics.DrawPath(pen:=myPen, path:=myPath) End Sub
If you use paths a lot, you will want to check out the Flatten method of the GraphicsPath class. This method allows you to change how items are stored within the object instance. By default, state is maintained for each item added to the path. This means that if a curve and an ellipse, for instance, are stored in a GraphicsPath, then data for the curve's points and control points as well as data that defines the ellipse is stored in the object. By flattening the path, you allow the object to manage the shape as a series of line segments, thus reducing overhead. In a completely flattened path, all points are stored as points to be connected by line segments.
Regions and Clipping
A region is a section of the screen defined by a given path or rectangle. Regions allow you to define clip areas and do hit-testing based on a graphics area. Clipping involves one shape defining the border, or area, of another shape. Additional items drawn within a defined region are constrained by the region; that is, a line with a width of 50 drawn within a rectangular region whose width is 20 will be cropped to 20 for display. Hit-testing simply allows your application to know when the user has placed the mouse over a given region or if another shape is contained within the area defined by the region. For example, if you define a region based on a rectangle, you can trap when a user clicks on the rectangle or when his or her mouse travels over the rectangle.
You use the Region class to create regions with the namespace. An instance of the Region class can be created with either a valid Rectangle instance or a Path object. To hit-test, you use the IsVisible method of the Region class. Once a region has been defined, you can pass a point or a rectangle and a valid graphics surface as parameters to the IsVisible method. This method simply returns True if the given point or rectangle is contained within the Region; otherwise, it returns False. The following is an example of this method call; it displays the return of the IsVisible method to a message box.
MsgBox(prompt:=myRegion.IsVisible(x:=75, y:=75, g:=myGraphics))
You still draw with the Graphics class, but the Region class allows you to set parameters for drawing. For example, you set the region parameter of the SetClip method of the Graphics object to your instance of Region. This tells the graphics object that your Region further defines the graphics area on the given drawing surface.
Listing 9.7 presents a clipping example. We first draw a rectangle and add it to a Path object. We then define a Region instance based on the Path object. After that, we call the SetClip method of our Graphics container and pass in the Region object. Finally, we draw a number of strings to the graphic surface; notice how our defined region clips them.
Listing 9.7 Clipping with the Region Class
Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click 'local scope Dim myGraphics As Graphics Dim myPen As New Pen(color:=Color.Blue, Width:=2) Dim myPath As New System.Drawing.Drawing2D.GraphicsPath() Dim myPoints(2) As Point Dim myRegion As Region Dim i As Short 'define a triangle myPath.StartFigure() myPoints(0) = New Point(x:=100, y:=20) myPoints(1) = New Point(x:=50, y:=100) myPoints(2) = New Point(x:=150, y:=100) 'add triangle to the path myPath.AddPolygon(points:=myPoints) 'create a region based on the path myRegion = New Region(path:=myPath) 'return the current form as a drawing surface myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle) 'draw the region's outline to the screen myGraphics.DrawPath(pen:=myPen, path:=myPath) 'set the clipping region myGraphics.SetClip(Region:=myRegion, _ combineMode:=Drawing.Drawing2D.CombineMode.Replace) 'draw the string multiple times For i = 20 To 100 Step 20 'draw clipped text myGraphics.DrawString(s:="Clipping Region", _ Font:=New Font(familyName:="Arial", emSize:=18, _ style:=FontStyle.Regular, _ unit:=GraphicsUnit.Pixel), _ brush:=New SolidBrush(color:=Color.Red), _ x:=50, y:=i) Next End Sub
Did you notice that when we called SetClip we also set something called the combineMode? This indicates how the two regions or shapes should be combined. In our case, we had a triangular Region object and a few strings that we drew. We set the combineMode enumeration to Replace to indicate that the string information should replace the region inside the triangle. Of course, there are a number of additional members to this enumeration. Table 9.9 lists them. Also, note that each enumeration member has a corresponding method on the Region class (Region.Replace for example).
Table 9.9 Region Combine Modes
Enumeration Member |
Example Output |
Description |
|
This is an example of the two objects that we are combining. The rectangle represents the clipping region. |
|
Complement |
The Complement member indicates that only the portion of the second region that does not intersect with the first should be updated. |
|
Exclude |
The Exclude member indicates that the intersecting portion of the second region should be excluded from the first region. The result is only points that belong to the first region specifically. |
|
Intersect |
The Intersect member indicates that only points common to both regions should be valid. |
|
Replace |
The Replace member indicates that the second region replaces the internal region defined by the first. That is, the first region now contains, or defines the area for, the second region. |
|
Union |
The Union member indicates that both regions should be joined. The result is an area that is defined by all points in both regions. |
|
Xor |
The Xor member is the opposite of the Intersect member. It contains all points that are not common to either region. |
The following code was used to create the example graphics in the previous table. Note that we created two regions and combined them using the Region.[Method] syntax.
'local scope Dim myGraphics As Graphics Dim myPath As New System.Drawing.Drawing2D.GraphicsPath() Dim myPath2 As New System.Drawing.Drawing2D.GraphicsPath() Dim myRegion As Region Dim myRegion2 As Region 'return the current form as a drawing surface myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle) 'create the first path and region myPath.StartFigure() myPath.AddRectangle(rect:=New Rectangle(x:=50, y:=50, Width:=50, _ Height:=20)) myRegion = New Region(path:=myPath) 'create the first path and region myPath2.StartFigure() Dim myRec As New Rectangle(x:=35, y:=45, Width:=50, Height:=30) myPath2.AddEllipse(rect:=myRec) myRegion2 = New Region(path:=myPath2) 'add the paths together myRegion.Complement(Region:=myRegion2) 'fill the region myGraphics.FillRegion(brush:=New SolidBrush(Color.Black), _ Region:=myRegion)
Suggestions for Further Exploration
Write code using the following methods of the GraphicsPath class: Warp and Widen.
Use the interior of text to outline a clipping region. Create a Path based on a string and use the path to create the Region.