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.
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.
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 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
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. } }; }
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.
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
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); } } }; }
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.
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.