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

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

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

458

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; ArrayList ^coords;

#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->Paint +=

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

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)

{

coords->Add(Point(e->X, e->Y)); Invalidate();

}

private:

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

{

for each (Point^ p in coords)

{

e->Graphics->DrawString(String::Format("({0},{1})",p->X,p->Y), gcnew Drawing::Font("Courier New", 8),

Brushes::Black, (Single)p->X, (Single)p->Y);

}

}

};

}

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 +

459

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.

The Invalidate Method

What is this Invalidate() method and why was it called? The Invalidate() method is the manual way of triggering a Paint event. Thus, in the previous example, because you no longer draw the coordinate information to the screen in the MouseDown handler, you need to trigger the Paint event using the Invalidate() method.

Calling the Invalidate() method without any parameters, as shown in the preceding example, tells the form that its entire client area needs updating. The Invalidate() method also can take parameters. These parameters allow the Invalidate() method to specify that only a piece of the client area within the control needs to be updated. You will look at this type of the Invalidate() method in GDI+ optimization later in the chapter.

GDI+ Coordinate Systems

When you rendered the strings earlier, you placed them where they were supposed to be on the screen by specifying pixel distances from the top-left corner, increasing the X-axis when moving to the right and increasing the Y-axis when moving down to the bottom (see Figure 11-5).

460

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 +

Figure 11-5. The default GDI coordinate system

A key aspect of GDI+ is that it is supposed to be device independent. How can that be, if everything is rendered based on a pixel standard? Pixels are only one of several coordinate systems supported by GDI+ (see Table 11-4). For example, instead of coordinate (100, 100), meaning 100 pixels to the right and 100 pixels down, the meaning could be 100 millimeters to the right and 100 millimeters down. To change the coordinate system to be based on a different unit of measure, you need to change the PageUnit property of the Graphics class to a different GraphicsUnit.

Table 11-4. GDI+-Supported GraphicsUnits

System

Description

Display

Specifies 1/75 of an inch as a unit of measure

Document

Specifies 1/300 of an inch as a unit of measure

Inch

Specifies 1 inch as a unit of measure

Millimeter

Specifies 1 millimeter as a unit of measure

Pixel

Specifies 1 pixel as a unit of measure

Point

Specifies a printer’s point or 1/72 of an inch as a unit of measure

 

 

It is also possible to move the origin (0, 0) away from the top-left corner to somewhere else on the drawing surface. This requires you to translate the origin (0, 0) to where you want it located using the Graphics class’s TranslateTransform() method.

The example in Listing 11-6 changes the unit of measure to millimeter and shifts the origin to (20, 20).

Listing 11-6. Changing the Unit of Measure and the Origin

namespace CorrectingCoords

{

using namespace System;

using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms;

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 +

461

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

}

protected:

~Form1()

{

if (components)

{

delete components;

}

}

private:

System::ComponentModel::Container ^components; ArrayList ^coords;

#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->Paint +=

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

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)

{

coords->Add(Point(e->X, e->Y)); Invalidate();

}

462

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 +

private:

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

{

for each (Point^ p in coords)

{

e->Graphics->DrawString(String::Format("({0},{1})",p->X,p->Y), gcnew Drawing::Font("Courier New", 8),

Brushes::Black, (Single)p->X, (Single)p->Y);

}

}

};

}

As you can see in NewUnitsOrigin.exe, it is possible to use multiple types of units of measure and origins within the same Paint event handler. Figure 11-6 displays a small rectangle, which was generated by the default pixel unit of measure and origin. The larger and thicker lined rectangle is what was generated when the unit of measure was changed to millimeter and origin was moved to (20, 20).

Figure 11-6. Changing the unit of measure and the origin

You should notice a couple of things in this example. First, the client size still uses pixel width and height. There is no PageUnit property for a form. Second, when you change the PageUnit of the Graphics class, all rendering from that point is changed to the new unit of measure. This is true even for the width of lines. Pens::Black creates lines 1 unit thick. When the unit is millimeters, Pens::Black will end up creating a line 1 millimeter thick.

Common Utility Structures

When you render your own text, shape, or image, you need to be able to tell the Graphics class where to place it and how big it is. It is not surprising that the .NET Framework class library provides a small assortment of structures and a class to do just that. Here they are in brief:

Point/PointF is used to specify location.

Size/SizeF is used to specify size.

Rectangle/RectangleF is used to specify both location and size at the same time.

Region is used to specify combinations of rectangles and regions.

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 +

463

All of these types use units of measure configured by the property PageUnit within the Graphics class. You need to take care that you always configure PageUnit consistently, or you might find that even though the same values are placed in these structures, they in fact represent different locations and sizes.

All the structures have int and float versions. Both provide the same functionality. The only real difference is the level of granularity that is supported in numeric values stored within the structures. In most cases, the int version will be good enough, but if you want finer granularity, you might want to choose the float version. Just remember that ultimately, the resolution of the drawing surface will decide how the shape, image, or text is displayed.

Point and PointF

As the name of this structure suggests, Point/PointF is an (x, y) location in units. Remember that units do not necessarily mean pixels. Pixels are only the default. The Point/PointF structure provides a few members (see Table 11-5) to aid in their manipulation.

Table 11-5. Common Point/PointF Members

Member

Description

+ operator

Translates a Point/PointF by a Size/SizeF.

- operator

Translates a Point/PointF by the negative of a Size/SizeF.

== operator

Compares the equality of two points. Both Xs and Ys must equal for the point

 

to equal.

!= operator

Compares the inequality of two points. If either the Xs or Ys don’t equal, then

 

the points don’t equal.

IsEmpty

Specifies if the point is empty.

Ceiling()

Static member that returns next higher integer Point from a PointF.

Offset()

Translates the point by the specified x and y amounts.

Round()

Static member that returns a rounded Point from a PointF.

Truncate()

Static member that returns a truncated Point from a PointF.

X

Specifies the x coordinate of the point.

Y

Specifies the y coordinate of the point.

 

 

To access the X or Y values within the Point/PointF structure, you simply need to access the X or Y property:

Drawing::Point a = Drawing::Point(10,15); int x = a.X;

int y = a.Y;

Casting from Point to PointF is implicit, but to convert from PointF, you need to use one of two static methods: Round() or Truncate(). The Round() method rounds to the nearest integer, and the Truncate() method simply truncates the number to just its integer value.

464 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 +

Drawing::Point

a = Drawing::Point(10,15);

Drawing::PointF

b = a;

Drawing::Point

c

= Drawing::Point::Round(b);

Drawing::Point

d

= Drawing::Point::Truncate(b);

The Offset() method is only found in Point, and it translates the point by the x and y coordinates passed to it.

a.Offset(2, -3);

The method is cumbersome as it returns void. I think it should return a Point type. I think it should also be a member of PointF.

Size and SizeF

Mathematically, Size/SizeF and Point/PointF are virtually the same. How they differ is really just conceptually. Point/PointF specifies where something is, whereas Size/SizeF specifies how big it is. Point/PointF and Size/SizeF even have many of the same members (see Table 11-6). The biggest difference is that sizes have widths and heights, whereas the points have x and y coordinates.

Table 11-6. Common Size/SizeF Members

Member

Description

+ operator

Adds two sizes together.

- operator

Subtracts one size from another.

== operator

Compares the equality of two sizes. Both Widths and Heights must equal for

 

the points to equal.

!= operator

Compares the inequality of two sizes. If either Widths or Heights don’t equal,

 

then the points don’t equal.

IsEmpty

Specifies whether the size is empty.

Ceiling()

Static member that returns the next higher integer Size from a SizeF.

Round()

Static member that returns a rounded Size from a SizeF.

Truncate()

Static member that returns a truncated Size from a SizeF.

Height

Specifies the height of the size.

Width

Specifies the width of the size.

 

 

It is possible to add or subtract two sizes and get a size in return. It is also possible to subtract a size from a point that returns another point. Adding or subtracting points generates a compiler error.

Drawing::Size sizeA = Drawing::Size(100, 100);

Drawing::Size sizeB = Drawing::Size(50, 50);

Drawing::Size sizeC = sizeA + sizeB;

Drawing::Size sizeD = sizeC - sizeB;

Drawing::Point pointA = Drawing::Point(10, 10) + sizeD;

Drawing::Point pointB = pointA - sizeC;

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 +

465

You can cast Point/PointF to Size/SizeF. What happens is the value of X becomes Width and the value of Y becomes Height, and vice versa. The following code shows how to implement all the combinations. It also shows the Size to SizeF combinations:

size = point; point = size; sizeF = pointF;

pointF = (Drawing::PointF)sizeF;

sizeF = (Drawing::Size)point; pointF = (Drawing::Point)size; sizeF = size;

size = Drawing::Size::Round(pointF); size = Drawing::Size::Truncate(pointF);

point = Drawing::Point::Round((Drawing::PointF)sizeF); point = Drawing::Point::Truncate((Drawing::PointF)sizeF); size = Drawing::Size::Round(sizeF);

size = Drawing::Size::Truncate(sizeF);

Rectangle and RectangleF

As I’m sure you can guess, the Rectangle/RectangleF structure represents the information that makes up a rectangle. It’s really nothing more than a combination of a Point structure and a Size structure. The Point specifies the starting upper-left corner and the Size specifies the size of the enclosed rectangular area starting at the point. There is, in fact, a Rectangle/RectangleF constructor that takes as its parameters a Point and a Size.

The Rectangle structure provides many properties and methods (see Table 11-7), a few of which are redundant. For example, there are properties called Top and Left that return the exact same thing as the properties X and Y.

Table 11-7. Common Rectangle/RectangleF Members

Member

Description

==

Returns whether the rectangle has the same location and size

!=

Returns whether the rectangle has different location or size

Bottom

Returns the y coordinate of the bottom edge

Ceiling()

Static member that returns the next higher integer Rectangle from

 

a RectangleF

Contains

Returns whether a point falls within the rectangle

Height

Specifies the height of the rectangle

Intersect()

Returns a Rectangle/RectangleF that represents the intersection of

 

two rectangles

IsEmpty

Specifies whether all the numeric properties are zero

Left

Returns the x coordinate of the left edge

Location

A Point structure that specifies the top-left corner

Offset()

Relocates a rectangle by a specified amount

466 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 +

Table 11-7. Common Rectangle/RectangleF Members (Continued)

Member

Description

Right

Returns the x coordinate of the right edge

Round()

Static member that returns a rounded Rectangle from a RectangleF

Size

A Size structure that specifies the size of the rectangle

Top

Returns the y coordinate of the top edge

Truncate()

Static member that returns a truncated Rectangle from a RectangleF

Union()

Returns a Rectangle/RectangleF that represents the smallest possible

 

rectangle that can contain the two rectangles

Width

Specifies the width of the rectangle

X

Specifies the x coordinate of the top-left corner

Y

Specifies the y coordinate of the top-left corner

 

 

The rectangle provides three interesting methods. The first is the Intersection() method, which can take two rectangles and generate a third rectangle that represents the rectangle that the two others have in common. The second is the Union() method. This method does not really produce the union of two rectangles as the method’s name suggests. Instead, it generates the smallest rectangle that can enclose the other two. The third interesting method is Contains(), which specifies whether a point falls within a rectangle. This method could come in handy if you want to see if a mouse click falls inside a rectangle.

The example in Listing 11-7 uses these three methods. This program checks whether a point falls within an intersection of the two rectangles or within the union of two rectangles. (Obviously, if the point falls within the intersection, it also falls within the union.)

Listing 11-7. Intersection, Union, or Neither

namespace

InterOrUnion

{

 

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();

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 +

467

// Build the rectangles from points and size Drawing::Point point1 = Drawing::Point(25,25); Drawing::Point point2 = Drawing::Point(100,100); Drawing::Size size = Drawing::Size(200, 150); rect1 = Drawing::Rectangle(point1, size);

rect2 = Drawing::Rectangle(point2, size);

}

protected:

~Form1()

{

if (components)

{

delete components;

}

}

private:

System::ComponentModel::Container ^components;

// intersecting and unions rectangles Drawing::Rectangle rect1; Drawing::Rectangle rect2;

#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(330, 300);

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

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

this->MouseDown +=

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

this->ResumeLayout(false);

}

#pragma endregion

private:

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

{

// Draw a couple of rectangles e->Graphics->DrawRectangle(Pens::Black, rect1); e->Graphics->DrawRectangle(Pens::Black, rect2);

}