
Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]
.pdf458 |
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);
}
}
};
}


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