The Graphics Class


So what is this magical Graphics class? It's the heart of all rendering activity of GDI+. It's a device-independent representation of the drawing surface that you plan to render graphics on. It can represent a monochrome display device like many PDAs or cellular phones, a true-color display device like those supported on a good number of computers used today, or anything in between. It can also be used for printers, from plotter to dot matrix to color laser.

Graphics Class Members

The Graphics class works by providing a large number of rendering methods (see Table 11-3) to developers that they will ultimately use to render their images. The rendering methods of the Graphics class can be divided into two groups: lines/outlines (draws) and fills. (The Clear() method is technically a fill.) Draws are used to outline open-ended and closed shapes or, in other words, they draw lines and outline shapes. Fills ... well, they fill shapes.

Table 11-3: Common Graphics Class Rendering Methods

METHOD

DESCRIPTION

Clear()

Clears the entire client area to the background color

DrawArc()

Draws a part of an ellipse

DrawClosedCurve()

Draws a closed curve defined by an array of points

DrawCurve()

Draws an open curve defined by an array of points

DrawEllipse()

Draws an ellipse

DrawIcon()

Draws an icon

DrawImage()

Draws an image

DrawImageUnscaled()

Draws an image without scaling

DrawLine()

Draws a line

DrawLines()

Draws a series of connected lines

DrawPie()

Draws a pie segment

DrawPolygon()

Draws a polygon defined by an array of points

DrawRectangle()

Draws a rectangle

DrawRectangles()

Draws a series of rectangles

DrawString()

Draws a text string

FillClosedCurve()

Fills a closed curve defined by an array of points

FillEllipse()

Fills an ellipse

FillPie()

Fills a pie segment

FillPolygon()

Fills a polygon defined by an array of points

FillRectangle()

Fills a rectangle

FillRectangles()

Fills a series of rectangles

Something that might disturb you a little bit is that there is no Graphics constructor. The main way of getting an instance of a Graphics class is by grabbing from

  • A PaintEventArgs's Graphics property

  • A control using its CreateGraphics() method

  • An image using the Graphics static FromImage() method

  • A handle to a window using the Graphics static FromHwnd() method

Usually you will only use PaintEventArgs's Graphics property or, as you will see in the "Double Buffering" section, the FromImage() method.

The Dispose Method

The Graphics object uses a lot of system resources. Some examples of Graphics objects are System::Drawing::Graphics, System::Drawing::Brush, and System::Drawing::Pen. It's important that if you create a graphics resource, you release it as soon as you're finished with it. You do this by using the Dispose() method. Basically, if you create an object that implements the IDisposable interface, you should call its Dispose() method as soon as you're done with it. This allows these resources to be reallocated for other purposes.

You're probably thinking, "Won't the garbage collector handle all this?" Yes, it will, but because you have no control over when the garbage collector will run on the object and because graphics resources are precious, it's better to call the destructor yourself. Be careful you only call the Dispose() method on objects you create. This way, you don't call it for the Graphics object you extracted from PaintEventArg, as you're just accessing an existing object and not creating your own. Listing 11-4 presents an example where you need to call the Dispose() method for a Graphics object.

Listing 11-4: The Problem with Using CreateGraphics

start example
 namespace DisappearingCoords {     using namespace System;     using namespace System::ComponentModel;     using namespace System::Collections;     using namespace System::Windows::Forms;     using namespace System::Data;     using namespace System::Drawing;     public __gc class Form1 : public System::Windows::Forms::Form     {     public:         Form1(void)         //...     protected:         void Dispose(Boolean disposing)         //...     private: System::ComponentModel::Container * components;         void InitializeComponent(void)         {             this->AutoScaleBaseSize = System::Drawing::Size(6, 15);             this->ClientSize = System::Drawing::Size(292, 265);             this->Name = S"Form1";             this->Text = S"Click and see coords";             this->MouseDown +=             new System::Windows::Forms::MouseEventHandler(this,Form1_MouseDown);         }     private:         System::Void Form1_MouseDown(System::Object * sender,                          System::Windows::Forms::MouseEventArgs * e)         {             Graphics *g = this->CreateGraphics();             g->DrawString(String::Format(S"({0},{1})",_box(e->X),_box(e->Y)),                 new Drawing::Font(new FontFamily(S"Courier New"), 8),                 Brushes::Black, (Single)e->X, (Single)e->Y);             g->Dispose();  // we dispose of the Graphics object because we                             // created it with the CreateGraphics() method.         }     }; } 
end example

Rendering Outside of the Paint Event

Now you'll examine CreateGraphics() in an example (see Listing 11-4) and see what happens when you minimize and then restore the window after clicking a few coordinates onto the form.

Figure 11-3 shows the program DisappearingCoords.exe with the coordinate strings clipped after resizing the form.

click to expand
Figure 11-3: Clipped rendered coordinate strings

The coordinates disappear! What's happening here? When you minimize a window or overlay it with another window, its graphics device memory is released back to the system resource pool. Thus, everything that was displayed on the graphics device is lost, along with all the coordinates that you clicked onto the drawing surface.

With the preceding logic, the only time that a coordinate string is drawn to the graphics device is during a mouse click. Because this is the case, there is no way of restoring the coordinates without at least one mouse click occurring. This is why you want to use the Paint event; it is automatically triggered whenever more of the drawing surface area is exposed, either because it was restored, resized, or something that was obscuring it was removed.

Added to this, because none of the information about what was displayed on the drawing surface is stored anywhere when the surface area is reduced, you need to store the coordinates that you previously clicked so they can all be restored. Listing 11-5 shows how to fix the shortcomings of the previous example.

Listing 11-5: Corrected Clipping Problem

start example
 namespace CorrectingCoords {     using namespace System;     using namespace System::ComponentModel;     using namespace System::Collections;     using namespace System::Windows::Forms;     using namespace System::Data;     using namespace System::Drawing;     public __gc class Form1 : public System::Windows::Forms::Form     {     public:         Form1(void)         {             coords = new ArrayList();  // Instantiate coords array             InitializeComponent();         }     protected:         void Dispose(Boolean disposing)         //...     private: System::ComponentModel::Container * components;     private: ArrayList *coords;         void InitializeComponent(void)         {             this->AutoScaleBaseSize = System::Drawing::Size(6, 15);             this->ClientSize = System::Drawing::Size(292, 265);             this->Name = S"Form1";             this->Text = S"Click and see coords";             this->MouseDown +=             new System::Windows::Forms::MouseEventHandler(this,Form1_MouseDown);             this->Paint +=             new System::Windows::Forms::PaintEventHandler(this, Form1_Paint);         }     private:         System::Void Form1_MouseDown(System::Object * sender,                           System::Windows::Forms::MouseEventArgs * e)         {             coords->Add( __box(Point(e->X, e->Y)));             Invalidate();         }     private:         System::Void Form1_Paint(System::Object * sender,                                    System::Windows::Forms::PaintEventArgs * e)         {             Graphics *g = e->Graphics;             for (Int32 i = 0; i < coords->Count; i++)             {                 Point *p = dynamic_cast<Point*>(coords->Item[i]);                 g->DrawString(String::Format(S"({0},{1})",__box(p->X),                                                               _box(p->Y)),                     new Drawing::Font(new FontFamily(S"Courier New"), 8),                     Brushes::Black, (Single)p->X, (Single)p->Y);             }         }     }; } 
end example

Figure 11-4 shows CorrectingCoords.exe, though it's hard to tell after it has been minimized, resized, and overlaid. Notice the rendered string still appears as expected.

click to expand
Figure 11-4: Correctly rendered coordinate strings

Now the MouseDown event handles the adding of the click coordinates to an array for safekeeping, and the responsibility of rendering the coordinates is back where it should be: in the Paint event handler (Form1_Paint ()). Notice that every time the drawing surface is painted, every coordinate is rewritten, which is hardly efficient. You will look at optimizing this later.

How does the control know when to trigger a Paint event when the mouse clicks it on? That is the job of the Invalidate() method.




Managed C++ and. NET Development
Managed C++ and .NET Development: Visual Studio .NET 2003 Edition
ISBN: 1590590333
EAN: 2147483647
Year: 2005
Pages: 169

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net