Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

.pdf
Скачиваний:
70
Добавлен:
16.08.2013
Размер:
24.18 Mб
Скачать

448

C H A P T E R 1 1 G R A P H I C S U S I N G G D I +

public ref class Form1 : public System::Windows::Forms::Form

{

public:

Form1(void)

{

InitializeComponent();

}

protected:

~Form1()

{

if (components)

{

delete components;

}

}

private:

System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code void InitializeComponent(void)

{

this->SuspendLayout();

this->AutoScaleDimensions = System::Drawing::SizeF(6, 13); this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; this->ClientSize = System::Drawing::Size(292, 273);

this->Name = L"Form1"; this->Text = L"Hello GDI+"; this->Paint +=

gcnew System::Windows::Forms::PaintEventHandler(this, &Form1::Form1_Paint);

this->ResumeLayout(false);

}

#pragma endregion private:

System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e)

{

Graphics ^g = e->Graphics; g->DrawString("Hello World!",

gcnew Drawing::Font("Arial", 16), Brushes::Black, 75.0, 110.0);

}

};

}

Figure 11-1 shows the results of the program HelloGDI.exe.

C H A P T E R 1 1 G R A P H I C S U S I N G G D I +

449

Figure 11-1. Results of “Hello World!” GDI+ style

As you can see, not much is new here. The big differences are the addition of the PaintEventHandler event handler and the using of an instance of the Graphics class. The rest of the code is identical to that of any program you looked at in the previous two chapters.

All controls generate a Paint event when they determine that it needs to be updated. The Form class happens to also be a child of the Control class. A Paint event is triggered whenever the control is created, resized, or restored, or when another control that had overlaid it is moved, re-exposing a portion or all of the overlaid control.

As was pointed out previously, this “Hello World!” example differs from the previous two chapters in that it implements an event handler, PaintEventHandler, and uses a Graphics class. PaintEventHandler has two parameters. The first parameter is the sender of the Paint event. In this case, it is the form, but it can be almost any control. The second parameter is a pointer to the PaintEventArgs class. It is from the PaintEventArgs class that you will get two important pieces of information: the Graphics class and the ClipRectangle or the area that needs to be updated on the form. You will learn about the ClipRectangle later in the chapter when you look at optimization.

The Graphics class is the key to GDI+, but I delay exploration of the class until its own section a little later in the chapter. For the example, all you need to know is that the Graphics class has a member method, DrawString(), that you will use to draw the string to the display device. To get access to the Graphics class, you usually extract its pointer from the PaintEventHandler parameter:

System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e)

{

Graphics ^g = e->Graphics;

The final piece of this “Hello World!” program is to actually render the “Hello World” string to the display device. The DrawString method takes a few parameters. This example shows rendering on the drawing surface, at location x equals 75 and y equals 100, in black, 16-point Arial font:

g->DrawString("Hello World!",

gcnew Drawing::Font("Arial", 16), Brushes::Black, 75.0, 110.0);

Something to note about rendering with GDI+ is that the location coordinates are based on the client area of the form or, more accurately, the control. Rendering to a location outside of the control will be clipped and won’t be visible. Don’t panic; you’ll see how to add a scroll bar so you can scroll over and make hidden renderings visible.

450

C H A P T E R 1 1 G R A P H I C S U S I N G G D I +

OnPaint vs. PaintEventHandler

There’s a second way of processing Paint events: the protected virtual OnPaint() method. Unlike what you’ve seen before, you don’t call the OnPaint() method. Instead, you need to override it and then let the system handle it when it’s called. Listing 11-2 shows the “Hello World!” program again, this time using the virtual OnPaint() method.

Listing 11-2. “Hello World!” Using OnPaint()

namespace

HelloGDI_OnPaint

{

 

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 ref class Form1 : public System::Windows::Forms::Form

{

public:

Form1(void)

{

InitializeComponent();

}

protected:

~Form1()

{

if (components)

{

delete components;

}

}

private:

System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code void InitializeComponent(void)

{

this->components = gcnew System::ComponentModel::Container(); this->Size = System::Drawing::Size(300,300);

this->Text = L"Hello GDI+";

this->Padding = System::Windows::Forms::Padding(0); this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;

}

#pragma endregion

C H A P T E R 1 1 G R A P H I C S U S I N G G D I +

451

protected:

virtual void OnPaint(System::Windows::Forms::PaintEventArgs ^e) override

{

Form::OnPaint(e);

Graphics ^g = e->Graphics; g->DrawString("Hello World!",

gcnew Drawing::Font("Arial", 16), Brushes::Black, 75.0, 110.0);

}

};

}

The results of HelloGDI_OnPaint.exe when run are identical to the PaintEventHandler version. Most of the code is the same as well. The first difference is that there’s no handling of the Paint event within the InitializeComponent() method. It isn’t needed because the OnPaint() method will handle the Paint events for you. That isn’t to say that you can’t have the handler. I see a possibility where a static set of graphic rendering activities are placed within the OnPaint() method and then a set of other graphic rendering activities are placed in multiple Paint event handlers and, based on conditions, dynamically delegated to the appropriate handler. However, you could do the same thing using an OnPaint() or a Paint event handler alone.

So what’s the difference (if any) between the OnPaint() method and the handler

PaintEventHandler? Isn’t the OnPaint() method just a prepackaged PaintEventHandler? I thought so, like many other people (I assume), but I was wrong. The fact is that the Control class’s OnPaint() method is actually in charge of executing all the delegated Paint event handlers. This means the only way you can be assured that a Paint event happens is by overriding the OnPaint() method, because it’s possible to disable the Paint event handlers from actually firing within the OnPaint() method. It’s a very simple thing to do—you just have to not call the base class Form::OnPaint() within the OnPaint() method.

As you can see, the first statement within the OnPaint() method is to call the base class version of itself:

virtual void OnPaint(System::Windows::Forms::PaintEventArgs ^e) override

{

Form::OnPaint(e); //...Do stuff

}

Placing the OnPaint() method first was a conscious decision on my part, as it can make a difference where the base method call is placed within the implementation of the method. Placing it first, as shown in the preceding code, indicates that you must handle all the other delegated Paint events first or, in other words, do the rendering specified within this OnPaint() method last. Now if you place the base method call after doing the rendering of the method:

virtual void OnPaint(System::Windows::Forms::PaintEventArgs ^e) override

{

//...Do stuff Form::OnPaint(e);

}

this indicates render first what is in this method, and then handle all other delegated Paint events. Both might be legitimate depending on what you want to do. Try the code in Listing 11-3 first by placing Form::OnPaint() as the first line in the overloaded method and then as the last.

452 C H A P T E R 1 1 G R A P H I C S U S I N G G D I +

Listing 11-3. Placing the OnPaint Base Class Method

namespace

OnPaintWhere

{

 

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 ref class Form1 : public System::Windows::Forms::Form

{

public:

Form1(void)

{

InitializeComponent();

}

protected:

~Form1()

{

if (components)

{

delete components;

}

}

private:

System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code void InitializeComponent(void)

{

this->SuspendLayout();

this->AutoScaleDimensions = System::Drawing::SizeF(6, 13); this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; this->ClientSize = System::Drawing::Size(292, 273);

this->Name = L"Form1"; this->Text = L"Form1"; this->Paint +=

gcnew System::Windows::Forms::PaintEventHandler(this, &Form1::Form1_Paint);

this->ResumeLayout(false);

}

#pragma endregion

protected:

virtual void OnPaint(System::Windows::Forms::PaintEventArgs ^e) override

{

//Form::OnPaint(e);

e->Graphics->DrawString("Hello GDI+",

gcnew Drawing::Font("Arial", 16), Brushes::Black, 75.0, 110.0);

C H A P T E R 1 1 G R A P H I C S U S I N G G D I +

453

Form::OnPaint(e);

}

private:

System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e)

{

e->Graphics->DrawString("Hello GDI+",

gcnew Drawing::Font("Arial", 16), Brushes::Purple, 75.0, 110.0);

}

};

}

Figure 11-2 shows OnPaintWhere.exe in action where the text “Hello GDI+” is in purple in this black-and-white image. Guess you’ll have to take my word for it.

Figure 11-2. The rendering results if the base OnPaint is placed last in the method

When Form::OnPaint() is placed on the first line, the text turns out black, as the OnPaint() method’s version of the DrawString() method is handled last. When Form::OnPaint() is placed at the end, on the other hand, the text is purple because the PaintEventHandler version of the DrawString() method is handled last. By the way, if you remove all the logic within the OnPaint() method, no text is displayed, because the PaintEventHandler is never triggered as Form::OnPaint() was not called to execute the delegated Paint events.

Now after saying all this, does it really matter if your OnPaint() method calls its base class version? The usual answer to this is “Not really.” If you don’t plan on using the Paint event handler yourself and the form that you created is never inherited (both normally being the case), then calling OnPaint() makes no difference. In fact, it might speed things up minutely if you don’t call it because it isn’t doing any unneeded method calls. (This is my take on it, though. The .NET Framework documentation says you should always call the base class method, so maybe you should take Microsoft’s word, as there might be some hidden reason that I’m unaware of. That said, so far I haven’t come across any problems.)

Which should you use: the OnPaint() method or the Paint event handler? I think the OnPaint() method, as it doesn’t have the event delegate implementation overhead. But because it’s easier to use than the Paint event (you only have to double-click the event handler in the Properties dialog box to add it) and the cost of the overhead is so minute, I use the Paint handler from this point on.

454

C H A P T E R 1 1 G R A P H I C S U S I N G G D I +

The Graphics Class

So what is this magical Graphics class? It’s the heart of all rendering activity of GDI+. It’s a deviceindependent representation of the drawing surface that you plan to render graphics on. It can represent a monochrome display device like many PDAs or cell 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 provides developers with a large number of rendering methods (see Table 11-3) from which they can choose 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

 

 

C H A P T E R 1 1 G R A P H I C S U S I N G G D I +

455

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.

Disposing of Resources with Deterministic Cleanup

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 object, you release it as soon as you’ve finished with it. You do this by calling the destructor for the object once you’re done with it. This allows the system resources associated with the Graphics object 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 use deterministic cleanup and call the destructor yourself.

Be careful to call the destructor only on objects you create. For example, you don’t call the destructor 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 destructor for a Graphics object.

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.

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 ref class Form1 : public System::Windows::Forms::Form

{

public:

Form1(void)

{

InitializeComponent();

}

456

C H A P T E R 1 1 G R A P H I C S U S I N G G D I +

protected:

~Form1()

{

if (components)

{

delete components;

}

}

private:

System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code void InitializeComponent(void)

{

this->SuspendLayout();

this->AutoScaleDimensions = System::Drawing::SizeF(6, 13); this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; this->ClientSize = System::Drawing::Size(292, 273);

this->Name = L"Form1";

this->Text = L"Click and see coords"; this->MouseDown +=

gcnew System::Windows::Forms::MouseEventHandler(this, &Form1::Form1_MouseDown);

this->ResumeLayout(false);

}

#pragma endregion

private:

System::Void Form1_MouseDown(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e)

{

Graphics ^g = this->CreateGraphics(); g->DrawString(String::Format("({0},{1})", e->X, e->Y),

gcnew Drawing::Font("Courier New", 8), Brushes::Black, (Single)e->X, (Single)e->Y);

delete g;

//

we delete the Graphics object because we

 

//

created it with the CreateGraphics() method.

}

};

}

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

C H A P T E R 1 1 G R A P H I C S U S I N G G D I +

457

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 ref class Form1 : public System::Windows::Forms::Form

{

public:

Form1(void)

{

InitializeComponent();

coords = gcnew ArrayList(); // Instantiate coords array

}