web.njit.eduklee/cpt450_s05/demos/the basics of gdi.d…  · web viewfigure 4: the documentcontext...

34
The Basics of GDI+ GDI+ is .NET's way to render low-level graphics. GDI+ uses drawing primitives, such as lines, curves, circles, and rectangles. In addition, GDI+ supports image manipulation as well as text rendering. by Markus Egger June 20, 2003 In graphical user interfaces such as Microsoft Windows, drawing on the screen is an important task. Everything displayed on the screen is based on simple drawing operations. Often, environments such as Visual Basic abstract those drawing operations away from the developer. However, the same drawing operations still take place under the hood. In Visual Studio .NET, developers have easy access to that drawing functionality whenever they need it through a technology called GDI+. Using GDI+, developers can easily perform drawing operations such as generating graphs or building custom controls. Whenever you create a Windows Forms (WinForms) application .NET uses (and includes) GDI+ by default. The System.Drawing namespace contains all GDI+ functionality (as well as a few sub-namespaces). GDI+ provides all the basic drawing features, such as drawing lines, curves, circles, ellipses, strings, bitmaps, and more. GDI+ also gives developers the ability to fill areas with colors, patterns, and textures. This article will explain some of the basic drawing mechanisms that GDI+ provides. The Graphics Object All GDI+ drawing is done using a Graphics object. This object has a large number of methods that encapsulate drawing operations on a drawing "canvas." GDI+ supports a number of drawing canvases including: windows, bitmaps, and printer pages. Depending on the type of drawing canvas, the Graphics object is created or retrieved in a number of different ways. If you want to modify the contents of a bitmap file you would create a Bitmap canvas using the FromImage() method. This article will focus on one type of canvas: the window. Drawing in a WinForms form is a somewhat peculiar task, because it is not enough to draw the graphic once. Instead, the "drawing" may have to be "refreshed" (re-drawn) whenever a window requires repainting. Repainting can happen whenever a user resizes a window or one window overlaps another window and then something brings the overlapped windows to the front. So how do you know when to start drawing? The easiest way is to listen to the Paint event that is fired by every window (or control for that matter). The Paint event is a very good choice for drawing-logic because

Upload: others

Post on 21-Mar-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

 

The Basics of GDI+GDI+ is .NET's way to render low-level graphics. GDI+ uses drawing primitives, such as lines, curves, circles, and rectangles. In addition, GDI+ supports image manipulation as well as text rendering. 

by Markus Egger June 20, 2003

In graphical user interfaces such as Microsoft Windows, drawing on the screen is an important task. Everything displayed on the screen is based on simple drawing operations. Often, environments such as Visual Basic abstract those drawing operations away from the developer. However, the same drawing operations still take place under the hood. In Visual Studio .NET, developers have easy access to that drawing functionality whenever they need it through a technology called GDI+. Using GDI+, developers can easily perform drawing operations such as generating graphs or building custom controls.

Whenever you create a Windows Forms (WinForms) application .NET uses (and includes) GDI+ by default. The System.Drawing namespace contains all GDI+ functionality (as well as a few sub-namespaces). GDI+ provides all the basic drawing features, such as drawing lines, curves, circles, ellipses, strings, bitmaps, and more. GDI+ also gives developers the ability to fill areas with colors, patterns, and textures. This article will explain some of the basic drawing mechanisms that GDI+ provides.

The Graphics ObjectAll GDI+ drawing is done using a Graphics object. This object has a large number of methods that encapsulate drawing operations on a drawing "canvas." GDI+ supports a number of drawing canvases including: windows, bitmaps, and printer pages. Depending on the type of drawing canvas, the Graphics object is created or retrieved in a number of different ways. If you want to modify the contents of a bitmap file you would create a Bitmap canvas using the FromImage() method. This article will focus on one type of canvas: the window.

Drawing in a WinForms form is a somewhat peculiar task, because it is not enough to draw the graphic once. Instead, the "drawing" may have to be "refreshed" (re-drawn) whenever a window requires repainting. Repainting can happen whenever a user resizes a window or one window overlaps another window and then something brings the overlapped windows to the front.

So how do you know when to start drawing? The easiest way is to listen to the Paint event that is fired by every window (or control for that matter). The Paint event is a very good choice for drawing-logic because it fires whenever you need to refresh the display, and because part of the Paint event arguments (parameters) is a handle to the Graphics object. You can access the Graphics object via the following C# code:

private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { Graphics g = e.Graphics; }

Visual Basic .NET supports the same construct:

Private Sub Form1_Paint( _ ByVal sender As Object, _ ByVal e As System.Windows.Forms.PaintEventArgs) _ Handles MyBase.Paint

Page 2: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Dim g As Graphics = e.Graphics End Sub

Once you have a Graphics object, you are ready to draw on the window.

Author's Note: Whenever I refer to a "window" as the drawing canvas, I also mean "control." Internally, controls are handled just like windows.

Page 1 of 4

Page 3: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Simple Line DrawingsA fundamental task performed by the graphics-object, is drawing lines and curves. You can use a number of methods for this purpose. In GDI+, unlike in regular (older) GDI, drawing lines and filling areas are two entirely different operations. When you draw lines, you must consider a number of fundamental things. For example, you need to choose what kind of line to draw.. Do you want a straight line, or a curve? Do you want a simple, single line, or do you want to draw a complex line composed out of many segments? Maybe you want to draw a closed shape that forms a completely enclosed area with an identical start and end point (such as a circle, rectangle, or polygon). Depending on the desired shape of the line, different methods are available to generate them.

Less obvious than the position and shape of the line, are the parameters for a line. You might ask, "What parameters can I specify for a line?" You can specify the following parameters: attributes such as color and thickness, start and end-points, and the shape of the end of a line such as whether the line ends in a rounded or square "head" or ends in an arrow.

In GDI+, lines are represented by Pen objects. Pens encapsulate all the attributes described above. GDI+ provides a number of default Pen objects, such as pens of different colors. The following code demonstrated drawing a simple straight line using different pens. (This is VB .NET code. C# developers add a semi-colon at the end of the line):

g.DrawLine(Pens.Red,10,10,200,100) g.DrawLine(Pens.Green,10,30,200,

120) g.DrawLine(Pens.Blue,10,50,200,140)If you want to adjust the thickness of the used pen, you need to instantiate a custom pen object. This example generates a five-pixel thick red pen and uses it to draw another line:

g.DrawLine( _ New Pen(Color.Red, 5), _ 10, 100, 200, 190)You instantiate the Pen using the line color and thickness as parameters. Once again, the C# version of the code is very similar: Simply add a semi-colon at the end and write the "new" keyword in lower case.

Figure 1 shows the result of all four lines of code listed above.

If you play with the DrawLine() method a bit, you will discover that it has a large number of overloads, though the result of these overloads is the same. You can just take different paths to your destination. I encourage you to experiment with the different options. For instance, you can draw circles and ellipses:

g.DrawEllipse(Pens.Red, _ 10, 10, 150, 80)Similarly, you can draw rectangles with this code:

g.DrawRectangle(Pens.Green, _ 20, 100, 120, 60)The following example draws a Bezier curve. Explaining the details of Bezier curves is beyond the scope of this article (Time to drag out your old math text books...):

Figure 1: Drawing lines using different Pens.

Page 4: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

g.DrawBezier(Pens.Blue, _ 170, 10, 250, 90, 170, 90, 250, 180)Figure 2 shows the result of these 3 drawing operations.

Page 2 of 4

Page 5: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Drawing Complex FiguresDrawing lines and rectangles works very well when you need to create custom Windows Forms controls. If you want to create more complex and artistic drawings such as diagrams, GDI+ lets you draw more complex shapes. In GDI+, this is accomplished using graphics paths.

GraphicsPath objects encapsulate a number of line segments. You add individual segments via drawing primitives, such as AddElipse() and AddLine(). GraphicsPath objects make it relatively simple to generate complex shapes by automatically connecting line segments. Consider the following code:

Dim Person As New GraphicsPath() Person.AddEllipse(23, 1, 14, 14) Person.AddLine(18, 16, 42, 16) Person.AddLine(50, 40, 44, 42) Person.AddLine(38, 25, 37, 42) Person.AddLine(45, 75, 37, 75) Person.AddLine(30, 50, 23, 75) Person.AddLine(16, 75, 23, 42) Person.AddLine(22, 25, 16, 42) Person.AddLine(10, 40, 18, 16) g.DrawPath(Pens.Blue, Person)This simple example generates the shape of a human (well... as close as I can get with my limited artistic abilities) and renders it on the screen as shown in Figure 3.

Note: The GraphicsPath class is a member of System.Drawing.Drawing2D. Make sure to import that namespace or reference the class by its fully qualified name.

Graphics QualityAt this point it is important to discuss the quality of the graphics you render. When you draw vertical and horizontal lines, quality is not a big concern because GDI+ draws lines simply by setting the colors of pixels that are all lined up in a row. When you draw lines at an angle (or curves), things get a bit more tricky. The pixels on your monitor do not correlate with the pixels that should be set based on the mathematical calculation of the drawn line. So the rendering system needs to decide what pixels to use and which ones to leave out. This process is known as aliasing.

Aliasing leads to poor looking drawings—you can clearly see a "step" or "jagged" effect. One solution to this problem is a technique known as anti-aliasing. Using this technique, the rendering engine uses different color variations for pixels that should only be partially included, leading to a much smoother appearance to the human eye.

You can tell GDI+ how you would like it to optimize a drawing. Consider the following code for instance:

Dim oPen As New Pen(Color.Blue, 3)

Figure 3: Drawing a simple person using a GraphicsPath.

Page 6: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

g.SmoothingMode = _ SmoothingMode.HighSpeed g.DrawBezier(oPen, _ 10, 10, 90, 90, 10, 90, 90, 180) g.SmoothingMode = _ SmoothingMode.AntiAlias g.DrawBezier(oPen, _ 50, 10, 130, 90, 50, 90, 130, 180) g.SmoothingMode = _ SmoothingMode.HighQuality g.DrawBezier(oPen, _ 90, 10, 170, 90, 90, 90, 170, 180)This renders three similar Bezier Splines at different quality settings. Figure 4 shows a magnified version of the result. Naturally, you want the high-quality version, but quality comes with a cost: performance. Which method you choose will depend on the performance requirements for your application.

Figure 4: Bezier splines drawn at different quality levels.

Filling ShapesAs mentioned before, GDI+ also offers ways to fill shapes. The techniques you use to fill shapes is very similar to drawing shapes, except for fill operations you use Brushes. A GDI+ Brush is similar to a GDI+ Pen, but Brushes are often much more powerful.

The following example shows how to draw an ellipse filled with a green brush:

g.FillEllipse(Brushes.Green, _ 10, 10, 150, 80)In a slightly more complex operation you can fill a shape with a pattern using something called a Hatch Brush. In this example, you can create a diagonal brick effect:

Dim oBrush As New HatchBrush( _ HatchStyle.DiagonalBrick, _ Color.Blue, Color.Firebrick) g.FillEllipse(oBrush, _ 10, 100, 150, 80)You can also choose to use a bitmap as a Brush. In this code snippet you see that I load one of the default images that ships with Windows into a Bitmap object, then create a TextureBrush based on that image, and use it to fill the ellipse:

Dim oBmp As New _ Bitmap("C:\WINDOWS\GREENSTONE.BMP") Dim oBrush2 As New TextureBrush(oBmp) g.FillEllipse(oBrush2, _ 200, 10, 150, 80)

Page 7: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Furthermore, you can create gradient brushes as in the following example:

Dim oRect As New _ Rectangle(200, 100, 150, 80) Dim oBrush3 As New _ LinearGradientBrush(oRect, _ Color.Red, Color.Blue, _ LinearGradientMode.Vertical) g.FillEllipse(oBrush3, _ 200, 100, 150, 80)I used a Rectangle object to first specify an area that I wanted to confine the gradient to. I then defined two colors, as well as an angle ("Vertical" in this case, but you could also use a numeric value).

Author's Note: The LinearGradientBrush class is a member of System.Drawing.Drawing2D.

Figure 5 shows a combined result for the last 4 examples.

Figure 5: Ellipses filled using different brushes.

I personally favor gradient brushes. I think shapes filled with a gradient look more professional than shapes filled with a single color. Consider Figure 6, which shows the human shape filled with two different brushes (solid and gradient). Here's the code that fills the "person" path I created before:

Page 8: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Figure 6: Shapes filled with gradients often look more professional than shapes filled with solid brushes.

Dim oRect As New _ Rectangle(0, 0, 100, 100) Dim oBrush As New _ LinearGradientBrush(oRect, _ Color.White, Color.Red, _ LinearGradientMode.Vertical) g.FillPath(oBrush, Person)Whenever you want to create a shape with a fill color as well as an outline (a technique sometimes also referred to as "cell shading"), you need to perform both actions separately (unlike in conventional GDI). Perform the fill operation first and render the outline second to make sure potential drawing inaccuracies do not "cut" through the outline.

Page 3 of 4

Page 9: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

The Coordinate SystemWhenever you use GDI+ to draw, you use the GDI+ coordinate system. By default, the coordinate system maps directly to the pixels on your monitor. However, you may want a different behavior. You can, in fact, transform the coordinate system if you have special needs. Consider the person shape you created above. The position of that shape is defined by the graphics path object you use. But what if you wanted to draw multiple copies of that shape multiple times in multiple locations?

The easiest way to do so is to alter the virtual coordinate system. The Graphics object offers a number of methods to do so. Here's an example:

g.DrawPath(Pens.Black, Person) g.TranslateTransform(75, 0) g.DrawPath(Pens.Black, Person) g.ResetTransform()This draws the first person shape at the default position, then moves the origin (point 0,0) of the coordinate system 75 pixels to the right, and draws the shape again. Without the ability to move the coordinate system around, you'd have to create another person path identical to the first one, but located at a different position.

You need to reset the transformation after GDI+ completes the drawing operation in your virtual coordinate system. Otherwise, GDI+ will offset your future drawings to the right.

ZoomingYou may notice that the person shape in my figures seems to be a bit bigger than the one you get when you run the samples. That's because I made GDI+ zoom the shape before I took the screen shot. I used the following scale transformation to do the zooming trick:

g.ScaleTransform(2, 2) g.DrawPath(Pens.Black, Person) g.ResetTransform()This zooms everything by a factor of 2 on both axes. You could zoom at different factors for each axis. For instance, you could leave the height of the person at the original level, but change the horizontal zoom:

g.ScaleTransform(2, 1)Of course, this makes the little guy look terribly overweight. Most of the time you should zoom at equal factors for both axes.

RotatingAnother interesting transformation of the coordinate system is its ability to rotate. This allows for fancy tricks such as rendering text at an angle:

g.TranslateTransform(100, 50) g.RotateTransform(35) g.DrawString("Cool Text", _ New Font("Arial Black", 20), _ Brushes.Blue, 0, 0) g.ResetTransform()

Page 10: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Figure 7: Drawing text at an angle using a transform and a subsequent rotate translation.

This example moves the origin to a new point and then performs a subsequent rotation. Drawing the text then becomes trivial, as you render it at (virtual) position 0,0. Figure 7 shows the result as well as an illustration of the performed transformations.

Figure 8: Drawing text at an angle using a rotate transformation with a subsequent transform translation.

Note that the order in which you perform transformations is of crucial importance. Figure 8 shows what happens if you change the order of transformations As you can see, the resulting position of the text string is different. because I moved the coordinate system. Transformations can be very tricky to do correctly. I generally recommend that you perform rotations after all coordinate movement.

Page 4 of 4 Markus Egger is the President and Chief Software Architect of EPS Software Corp., located in Houston, Texas. He is also the founder of EPS Software Austria, located in Salzburg. Markus concentrates on consulting and development of custom software based on Microsoft technologies. His passion lies with object-oriented technology. Markus is an international author and speaker, and is the publisher of CoDe Magazine. For the past 8 years, Markus has received the Microsoft MVP award, originally for Visual FoxPro, and now for C#. Several applications he has worked on (mostly as project manager) have received Microsoft Excellence Award nominations. You can find a full bio on the Web at http://www.eps-cs.com and http://www.MarkusEgger.com. Email Markus at [email protected]

Page 11: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

 

GDI+ Drawing Page, Part IWith the .NET Forms and Drawing class libraries you can create a drawing application C! la Visio that allows users to drag and draw shapes, select those shapes, and move them about the form. 

by Lars Powers, MCSD, June 20, 2003

Mike Snell, MCSD  

Page 12: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

The RequirementsThe first step is to determine what the basic requirements are for a drawing project. We started by examining a few drawing applications on our laptops in an attempt to visualize some of the problems these types of applications have to solve. We looked at Microsoft Word with its visual representation of a page. Some items of note included things like margins, scrolling, and zooming. Visio also contains many of the characteristics we are interested in (see Figure 1).

By examining Visio, it was evident that there are a lot of basic requirements that a drawing program will have to solve before it can be useful to its user base. We started listing these requirements and were at first overwhelmed. However, after trimming, we were able to cut the list back to a somewhat manageable subset (at least for the first release). The desired application will provide the following features:

A visual illustration of a page's dimensions based on actual physical size of a printed piece of paper

A visual indication of the page's margins A visual grid that sits in the background and allows users to align objects Draw shapes such as lines, rectangles, and ellipses Add blocks of text to the page as well as embed those blocks inside a graphical shape Select an object and move it about the form Save documents to disk and open them at a later time Print and print preview documents Zoom in and out on the page and continue drawing Rotate shapes and text on the page Scale shapes and text elements

You can see that an application that sounds pretty straightforward can soon suffer from major scope creep (and this was the pared-down list). From this list we knew we had to further narrow the requirements to a more manageable scope if we were going to make our deadline. (If you can't extend the schedule, you have to cut features!). In the end we cut the list down to the following features:

A visual illustration of a page's dimensions based on actual physical size of a printed piece of paper

Draw rectangles and ellipses Select an object and move it about the form Save documents to disk and open them at a later time

This subset will define the scope for this multi-part article. At the end we will have illustrated, and solved, a number of issues that developers will encounter when designing and coding a drawing application.

Page 2 of 7

 

Figure 1: The Visio drawing page with its rulers, blue borders, and grid lines.

Page 13: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

The Drawing Page User ControlThe first technical design decision we made was to encapsulate the concept of a page as a user control. This will allow developers to add a page to any form or application and quickly expose, to their users, items like drawing, selecting, and moving graphical elements.

This user control will have to be able to react to user input, capture it, and be able to store that input. To capture some design decisions we constructed an object model to support the user control. Figure 2 shows the public interface into this control.

 

Figure 2: The PageControl object model illustrates the classes and their public interfaces.

Each class in the object model encapsulates a specific portion of the functionality that the user control will provide. The following describes each of these classes relative to their intended features:

PageControl. The PageControl class provides the visual representation of the control (or page). It is the actual user control that a user can drop onto another form. Users of the control will draw on it with a mouse, and therefore the control's main purpose is to respond to that type of input. Each instance of the PageControl class works directly with an instance of the Document class.

Document. The Document class, as its name implies, represents an actual document. In our case, a document is a physical file that can contain graphical elements, can be saved to disk, and can be re-opened at a later time. The Document class works with an instance of the Elements class to represent the group of graphical elements in the document.

Elements. The Elements class is simply a collection class (derived from CollectionBase) that represents all of the graphical elements in a given document. The Elements class groups Element items.

Element. The Element class is an abstract base class ('MustInherit' in Visual Basic .NET) that represents an element in a document (like a rectangle). This class defines the basic make-up of all elements that you can place into a document. A number of classes will derive from this base class. At the moment, only the ellipse and rectangle element classes are defined as concrete versions of Element. In the future, we may add a number of additional derived classes.

Page 14: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Now that we had a basic object model in place, we set about solving some of the other key requirements for the control.

Drawing Rectangles and EllipsesTo draw shapes onto the screen we needed more detail about our requirements. We again took inspiration from Visio:

The user should be able add shapes with the mouse (rather than via dialog boxes). The user should be able to see what they are drawing as they draw it. That is, the

application should draw an outline of the drawing as the mouse moves. The user should be able to pick their fill color, pen color, and line thickness.

Drawing elements should imply a natural order (or zOrder). That is, an element drawn last would be on top of other elements that it might overlap. This will allow the developer to add layering functionality (send-to-back / bring-to-front) to the application.

Page 3 of 7

Page 15: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Capturing Mouse EventsTo satisfy the first requirement, we knew we had to write code that captures mouse movements and clicks. These mouse events have to provide functionality for the users to draw, select, and move graphical elements about the page. The mouse events that our application needs to capture include:

Mouse Down. Triggered when a user pushes the left mouse button down.

Mouse Move. Triggered when a user moves the mouse in any direction.

Mouse Up. Triggered when a user releases the left mouse button (after pressing down).

The MouseDown EventFor the MouseDown event you simply need to capture the fact that a user has clicked the mouse button. The MouseMove event will use this information to allow drawing based on dragging the mouse. You first expose a property of the PageControl class called Draw. This property is of the type DrawType: an enumeration created to indicate what a user might want to draw. The values for an enumeration are Rectangle, Ellipse, and Nothing. The next step is to initialize a local variable for the Draw property and set its default value to DrawType.Nothing. Before a user can draw anything, they will have to indicate to the control (via the Draw property) what exactly they want to draw.

The next step is to wire up the MouseDown event via a private method, OnMouseDown. Listing 1 shows the complete code for this event.

For drawing purposes you only need to capture two facts inside the event, mouse down and position. Set the local Boolean member variable, m_capture, to true to indicate the user had clicked their mouse. The MouseMove event uses this variable to know when it is in "capture" mode.

You also capture the position (or point) on the screen of the user's cursor where he/she pressed the mouse button. Store this in a member variable to the control called m_sP (for starting point) and make its type is a System.Drawing.Point object. This structure allows you to store both the x and y coordinates of where the mouse was pressed. These coordinates represent a fixed starting point on the form for whatever a user might draw.

The MouseMove EventNow that you can tell when a user presses the mouse button down, you use this information inside of the MouseMove event to simulate drawing while a user drags their mouse. First, you capture another point to represent the position to where the mouse moved. This point is passed to you on the event's signature via the type MouseEventArgs (e). To store this value, create a new local variable as follows:

Point mp = new Point(e.X, e.Y);Once you have captured both the starting point (from the MouseDown event) and the moved-to point, you can use these points to calculate the distance that the mouse moved vertically (height) and horizontally (width) as follows:

int w = Math.Abs(mp.X - m_sP.X); int h = Math.Abs(mp.Y - m_sP.Y);Using this information you can paint an object (rectangle or ellipse) that represents the user's dragging. You use a member variable called m_captureR to contain the bounds of these two points as a System.Drawing.Rectangle instance. All that is left is to construct this Rectangle instance in the MouseMove event and then draw the same rectangle to the screen via the form's

Page 16: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Paint event.

However, one challenge remains. A user can move the mouse in any direction from the fixed starting point captured in the MouseDown event. For instance, a user might drag the mouse down and to the right. This would result in a drawing that moved from left to right and top down. However, the user could just as easily move the mouse to the left and up (or any other combination across the x and y axis). To handle this, you need some basic code that will check which direction the mouse is moving (based on positive and negative comparisons) and then create the rectangle accordingly. For example, if the mouse is moving up and to the left relative to the fixed starting point, you need to set the bounding rectangle's upper left corner to the move point and its bottom right corner to the start point. You can see the full source code for these checks in Listing 2.

Page 4 of 7

Page 17: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

The Paint EventNow that you know how to capture the bounds of the mouse's movement via a corresponding Rectangle (m_captureR), you need to display this rectangle to the user. To do so, force the control to re-paint using the following line of code inside the MouseMove event:

this.Invalidate();The Invalidate method of the Control class allows a specific region of the control to be invalidated, which forces it to be repainted. You can intercept this repaint inside the control's Paint event. You can see a complete listing of this event in Listing 3.

The code to display the drawn shape to the user turns out to be pretty straightforward. You first have to verify that the application is in capture mode by checking the member variable, m_capture. You also have to make sure that the user has indicated that they intend to draw something (m_draw). The resulting If statement looks like this:

if (m_capture & m_draw != DrawType.Nothing) { DrawCaptureR(g); }Drawing the captured rectangle is just as simple. You create a private routine that takes a reference to a valid System.Drawing.Graphics instance as a parameter (passed in from the PaintEventArgs in the Paint event). This routine, called DrawCaptureR, renders the captured rectangle to the control.

To accomplish this rendering feat, first create an instance of the System.Drawing.Pen class. This class, as its name implies, represents a pen that has an ink color and line thickness. For the ink color, let's use a green brush provided by the System.Drawing.Brushes class. Set the pen's thickness (or width) to 1 point as follows:

Pen p = new Pen(Brushes.Green, 1);Next use the System.Drawing.Drawing2D.DashStyle enumeration to make the pen look like a series of dashes:

p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;Finally, verify what the user intended to draw to the control (via the local m_draw) and then output the element to the control's surface using the appropriate method of the Graphics class (in this instance, DrawRectangle):

if (m_draw==DrawType.Rectangle) g.DrawRectangle(p, m_captureR); else g.DrawEllipse(p, m_captureR); The MouseUp EventNow that you have successfully captured the drag effect of the mouse and rendered the results to the page control's surface, you need to lock these results to the page once the user releases their drag (by releasing the mouse button). In addition, you need to store this element so it could be re-drawn to the screen later if need be.

To handle these tasks, you intercept the control's MouseUp event. Inside this event (complete code in Listing 4), you turn off capture mode by setting m_capture to false. This stops the control from processing further mouse movement via the MouseMove event. Next, you need to store the resulting graphical element in the object. To do so, you create a variable of the type Element. Then, based on the current drawing type (rectangle or ellipse), you cast that variable as the concrete Element class.

Page 18: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

el = new RectElement( m_captureR.Location, m_captureR.Size, new Pen(Brushes.Black, 2), m_fill);Now you need to store this element inside the Document. If you remember from the object model, the Document class that you create maintains a reference to the Elements collection. And of course, the PageControl class holds a reference to the Document class. Therefore, from within the PageControl's MouseUp event you have to add the newly created Element instance to the Elements collection as follows:

m_doc.Elements.Add(el);One requirement for each Element instance is that it can draw itself. Therefore, all that was left to do in the MouseUp event was force the control to repaint. The control's Paint event will then handle drawing any elements stored in the associated Document instance.

To draw these stored elements, look at the private routine inside the PageControl class—DrawElements. The PageControl's Paint event calls DrawElements, passing it a handle to the control's Graphics surface. This routine simply loops through the Elements collection and calls the Draw method of each Element instance (forwarding it the Graphics handle).

The Draw methods for each of the Element classes are all very similar. First create a GraphicsContainer instance with the call:

GraphicsContainer gc = g.BeginContainer();This call to BeginContainer allows you to cache subsequent calls to the Graphics handle before rendering to the screen.

Next, create a System.Drawing.Rectangle instance and draw it to the Graphics surface, then fill accordingly. These calls are as follows:

Rectangle r = new Rectangle(this.Position, this.Size); g.DrawRectangle( new Pen(new SolidBrush(this.PenColor), this.PenThickness), r); g.FillRectangle( new SolidBrush(this.FillColor), r);Finally, call the EndContainer method of the Graphics class to indicate that your application can now render the given element to the user's screen:

g.EndContainer(gc);Page 5 of 7

Page 19: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Selecting and Moving ElementsThe next requirement to tackle is to allow a user to select and then drag a graphical element on the page. This will again involve our mouse events (MouseDown, MouseMove, and MouseUp). From an algorithm perspective, the code needs to handle the following sequence of events:

1. Determine when the user clicks the mouse and they are not intending to draw. 2. Check to see if the user's click intersects with any of their previously drawn elements. 3. Visually indicate the selection to the user. 4. Re-draw the element as the user moves it about the page. 5. Re-lock the element on the page.

The first requirement is obviously the simplest, just an If statement inside of the MouseDown event (see Listing 1).

As for the second requirement, you need to write code to solve a common problem called "hit-testing." Hit-testing involves capturing the point of the user's cursor at the time of their click and then determining if that captured point is within the bounds of any other element.

For simplicity sake, in this version, the code only supports single item select (no multi-select) and drag. In addition, elements are hit-tested in the reverse order that they were drawn to the screen (or added to the Elements collection class). This way, if two elements overlapped, the top-most Element would be selected every time. Let's look at the code that handles this.

First, add a routine to the Elements collection class called GetHitElement. This routine takes a Point (testPoint) type that represents the user's click (and our testing point). The routine returns the hit (or user selected) element (or a null in the case of no selection). You call this method from the MouseDown event on the control:

Element el = m_doc.Elements.GetHitElement(m_sP);Inside the GetHitElements method you simply loop the collection backwards and call each Element's internal HitTest method as follows:

//search the list backwards for (int i=this.List.Count; i>0; i--) { Element e = (Element)this.List[i-1]; if (e.HitTest(testPoint)) return e; }Next you need to make each concrete Element class expose their own HitTest method. This is required because each element's shape can be different (rectangle vs. ellipse, for example). Thankfully, GDI+ makes hit-testing pretty simple. First create a System.Drawing.Drawing2D.GraphicsPath instance:

GraphicsPath gp = new GraphicsPath();You'll use the GraphicsPath object to contain a version of the given Element. Therefore, you add the element to the graphics path:

gp.AddRectangle( new Rectangle(this.Position, this.Size));Next, you check the value of the IsVisible property of the GraphicsPath instance to determine if the user's click point is inside the element:

Page 20: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

return gp.IsVisible(testPoint);If the call to IsVisible returns true, you have a hit. Once you have determined the user's selected object, you need to indicate that visually to the user. To do this you'll add code to the PageControl's MouseDown event. Once again you'll leverage the object model. Now set the hit element to the Document's SelectedElement property, which allows you to add code to the control's OnPaint event that checked to make sure that a SelectedElement exists. If so, the Paint event tells the selected element to draw itself as follows:

if(m_doc.SelectedElement != null) m_doc.SelectedElement.DrawSelected(g);Of course, you have to create a DrawSelected method for each concrete Element class. In this case, to represent selection the code will just draw the object's outline in blue since the application does not allow the user to create objects with anything other than black pens.

Finally, to illustrate movement of the selected element about the page, you reset the selected element's Position property to that of the newly moved-to position. Before doing this, you need to set the moved point's offset values based on a difference calculated in the MouseDown event:

mp.Offset(m_xDiff, m_yDiff); m_doc.SelectedElement.Position = mp;Page 6 of 7

Page 21: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Saving and Opening a DocumentThe last requirement of this control's library was that it be able to save a document to disk and re-open it at a later time. To give users this ability, you will add both a Save and Open method to the PageControl. These methods save and open instances of the PageControl's Document class to create and open binary versions of the Document class.

To manage this you will first mark the Document class (and its associated classes) as Serializeable. This allows the .NET Framework to serialize and de-serialize an object into one of multiple formats (in our case binary). You mark the class via an Attribute class as follows:

[Serializable()] public class DocumentNext, create the Save routine. This routine uses BinaryMessageFormatter to serialize the Document class out to a file as follows:

public void Save(string fileName) { Formatters.Binary.BinaryFormatter bf = new Formatters.Binary.BinaryFormatter(); bf.Serialize( new FileStream(fileName, FileMode.Create), this.m_doc); }To open the serialized class you need to create another binary formatter. This time you call the Deserialize method and cast the results into a new Document instance that gets stored as the PageControl's associated Document instance. The code looks like the following:

Formatters.Binary.BinaryFormatter bf = new Formatters.Binary.BinaryFormatter(); m_doc = new Document(); m_doc = (Document)bf.Deserialize( new FileStream(fileName, FileMode.Open));Surprisingly, you've done everything necessary for saving and opening instances of the custom Document class. Users can save their documents and open them at a later time. Of course, a developer that consumes the PageControl will still have to wire up the actual interaction with the file system.

The Application Container (Main)You now have a fully functioning, version 1 PageControl. However, to see it in action (and to test it) you need to create an application that consumes the control. Keeping in mind the Visio paradigm, it makes sense to create a container for the application that could host multiple new documents. To handle this requirement you simply create a standard Windows Form and set its IsMdiContainer property to true. An MDI container also offers a very familiar paradigm to users.

You can add a number of controls to this main form to make it more useful. To handle basic navigation, add a menu bar and toolbar. Next, add the open and save common dialog controls. These controls will help you quickly and consistently write the code for interacting with the file system during opening and saving a Document. Finally, let's add the color dialog control to allow users to choose a fill color for drawing elements that they'll draw on a page. Figure 3 shows an example of this MDI container.

Page 22: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

 Figure 3: The MDI container form.

Wiring up these controls resulted in some pretty basic code (although showing the currently selected fill color on the toolbar required a little GDI+ work). This code is included in the sample download.

The Page HostNow that you have a document hosting environment with an MDI interface, you must create a host for the actual PageControl user control. This host will be another Windows Form, and we'll call it DocumentContext. Its main job is to contain the newly created PageControl and act as a MDI child window to the MDI container. Figure 4 provides a screen shot of the DocumentContext MDI child form and our PageControl class in action.

 Figure 4: The DocumentContext MDI child form with an instance of the PageControl user control.

This child window has one requirement: maintain the page in its center. Similar to Visio or Word, when a user re-sizes the form (DocumentContext), the page should remain in the center of the form with a standard border on both sides. This provides the application a more polished feel.

At first glance, the standard docking and anchoring properties available in Windows Forms applications appear to provide the necessary mechanism to make this feature happen. After some monkeying around, however, it soon becomes apparent that additional code was in order.

Page 23: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Thankfully, the Layout event lends itself handily to the problem. This event gets fired when you need to move on the form, such as a user resizing the form.

The code added to this event represents some straightforward math: Capture the new width of the DocumentContext form and subtract the width of the page control (set based on the Orientation property) and divide by 2 to provide an equal border on both the left and the right. Use the resulting value to set the page control's Left property. You can find the complete code for this event in Listing 5.

In this article you've created the foundation for a drawing page control (see Figure 5 for a final look). In Part 2 you will learn how to tackle a number of other requirements for the control including adding text blocks, zooming in and out on the page, and scaling elements. Each of these requirements will present themselves with a whole new set of issues.

 Figure 5: The application and control in action.

Download the complete source code for this article at: www.brilliantstorm.com/resources

Page 7 of 7 Lars Powers, MCSD has over 10 years of experience analyzing business problems and developing software solutions. Most of his experience centers on leading development teams and writing software in Microsoft development environments. Lars & Mike have formed brilliantStorm, a partnership focused on providing developers with .NET mentoring and consulting. Their latest book is now available: Visual Basic Programmer's Guide to the .NET Framework Class Library. [email protected]. Mike Snell, MCSD has over 12 years of experience writing and designing software. His experience centers on creating enterprise-level, Web-based systems using the Microsoft platform. [email protected]

Page 24: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Listing 1.

MouseDown event

private void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e) { //purpose: capture the mouse down event //indicate we are capturing m_capture = true; //get the down-point of the cursor (starting point) m_sP = new Point(e.X, e.Y); //release any selected elements m_doc.SelectedElement = null; this.Invalidate(); //verify it is time to draw if (this.Draw == DrawType.Nothing) { //test for "hits" (selected elements) Element el = m_doc.Elements.GetHitElement(m_sP); if (el != null) { //set the selected item m_doc.SelectedElement = el; //set offset differences in case of moving the object m_xDiff = m_doc.SelectedElement.Position.X - m_sP.X; m_yDiff = m_doc.SelectedElement.Position.Y - m_sP.Y; //change the cursor this.Cursor = Cursors.SizeAll; //force the form to re-draw this.Invalidate(); } } }

Page 25: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Listing 2.

MouseMove event

private void OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e) { //purpose: capture the mouse move event //determine if we are in capture mode if (m_capture) { //create a point that is the mouse moved point Point mp = new Point(e.X, e.Y); //calculate the width of the move int w = Math.Abs(mp.X - m_sP.X); //calculate the height of the move int h = Math.Abs(mp.Y - m_sP.Y); //verify the start point of the rectanlge // as the user can move in any direction relative // to the starting point (mouse down) if (mp.X > m_sP.X & mp.Y > m_sP.Y) { m_captureR = new Rectangle(m_sP.X, m_sP.Y, w, h); } else if (mp.X < m_sP.X & mp.Y < m_sP.Y) { m_captureR = new Rectangle(mp.X, mp.Y, w, h); } else if (mp.X > m_sP.X & mp.Y < m_sP.Y) { m_captureR = new Rectangle(m_sP.X, mp.Y, w, h); } else if (mp.X < m_sP.X & mp.Y > m_sP.Y) { m_captureR = new Rectangle(mp.X, m_sP.Y, w, h); } if(m_doc.SelectedElement != null) { //we are not drawing, and we have a selected an object // reset the position of the selected object // using offset values stored at the //time of mouse-down mp.Offset(m_xDiff, m_yDiff); m_doc.SelectedElement.Position = mp; } //cause the page control to be repainted this.Invalidate(); }

Page 26: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

}

Page 27: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Listing 3.

Paint event

protected override void OnPaint System.Windows.Forms.PaintEventArgs e) { //get reference to graphics object Graphics g = e.Graphics; //draw page this.DrawPageBounds(g); //re-draw all elements this.DrawElements(g); //draw the rectangle if capturing in draw mode if (m_capture & m_draw != DrawType.Nothing) { DrawCaptureR(g); } else { //draw the selected element if applicable if(m_doc.SelectedElement != null) m_doc.SelectedElement.DrawSelected(g); } }

Page 28: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Listing 4.

MouseUp event

private void OnMouseUp( object sender, System.Windows.Forms.MouseEventArgs e) { //purpose: capture the mouse up event //indicate to stop capturing m_capture = false; if (m_draw != DrawType.Nothing) { //define an element var Element el = null; if (m_draw == DrawType.Rectangle) { //create a new rectangle element el = new RectElement(m_captureR.Location, m_captureR.Size, new Pen(Brushes.Black, 2), m_fill); } if (m_draw == DrawType.Ellipse) { //create a new ellipse element el = new EllipseElement(m_captureR.Location, m_captureR.Size, new Pen(Brushes.Black, 2), m_fill); } //add the element to the local collection m_doc.Elements.Add(el); } if (m_doc.SelectedElement != null) { //change the cursor back this.Cursor = Cursors.Default; } //allow the page to re-paint this.Invalidate(); }

Page 29: web.njit.eduklee/CPT450_S05/demos/The Basics of GDI.d…  · Web viewFigure 4: The DocumentContext MDI child form with an instance of the PageControl user control. This child window

Listing 5.

MDI child (DocumentContext)

private void DocumentContext_Layout (object sender, System.Windows.Forms.LayoutEventArgs e) { //purpose: respond to the layout event //used to center the page control on the host //calculate the left position of the page int leftPos = (this.Width - pgControl.Width)/2; //do not go below frame size if (leftPos < pgControl.FrameSize) {leftPos = pgControl.FrameSize;} //position the control on the form pgControl.Left = leftPos; pgControl.Top = 0; //force redraw of the control pgControl.Invalidate(); }