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

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

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

478

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 +

array<FontFamily^>^ fontfamilies = availFonts->Families;

Random ^rand = gcnew Random(); int ff, s, fs, u;

for (int i = 0; i < fonts->Length; i++)

{

s = rand->Next(0,3); fs = rand->Next(0,3);

u= rand->Next(0,2);

//Not all fonts support every style do {

ff = rand->Next(0,fontfamilies->Length);

}

while (!fontfamilies[ff]->IsStyleAvailable( (FontStyle)fontstyles[fs]));

//Display string of font

fontstr[i] = String::Format("{0} {1} {2}", fontfamilies[ff]->Name,

sizes[s], String::Concat(fontstyles[fs], " ",

units[u]));

// Create the font

fonts[i] = gcnew Drawing::Font(fontfamilies[ff], sizes[s], (FontStyle)fontstyles[fs], (GraphicsUnit)units[u]);

}

}

protected:

~Form1()

{

if (components)

{

delete components;

}

}

private:

System::ComponentModel::Container ^components; array<Drawing::Font^>^ fonts;

array<String^>^ fontstr;

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

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 +

479

this->Name = L"Form1"; this->Text = L"Many Fonts"; 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)

{

float lineloc = 0;

for (int i = 0; i < fonts->Length; i++)

{

// Display font

e->Graphics->DrawString(fontstr[i], fonts[i], Brushes::Black, 10, lineloc);

// Calculate the top of the next line lineloc += fonts[i]->Height;

}

}

};

}

Deep within the code is the routine to get a list of all the font families on your system:

InstalledFontCollection ^availFonts = gcnew InstalledFontCollection(); array<FontFamily^>^ fontfamilies = availFonts->Families;

After these two lines are run, you have an array of all FontFamilies on your computer. It is pretty easy, no? The only hard part is remembering to add the namespace System::Drawing::Text, which you need to get access to the InstalledFontCollection class.

Something you might want to notice is how I figured out where to start the next line of String. I did this by adding the height of the font to the current line y coordinate after I finished drawing with it:

lineloc += fonts[i]->Height;

Figure 11-11 shows one instance of FontsGalore.exe running. I doubt you will ever see the same combination of fonts displayed twice.

Figure 11-11. Displaying random fonts

480

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 +

Colors

Most current display device technology defines colors by breaking them up into their three basic components: red, green, and blue. Depending on the configuration of the display device, these components usually will have a value that ranges from 0 to 255. The principle is that by combining different amounts of red, green, and blue, you can generate any color. Thus, many of today’s display devices can display up to 16,777,216 (256 cubed) unique colors.

But the story doesn’t end there. Colors also provide an alpha component. This component represents how transparent the color is. If the alpha value is 0, then the color is completely transparent (a kind of useless color), and a value of 255 is completely opaque. In between these two points are varying degrees of transparency that will, when drawn to the screen, merge with any color already existing at that location. You see this effect used most often in computer games.

Many of the Graphics class’s Drawing methods need a System::Drawing::Color structure containing one of the colors built from the values described previously before they can be used. The Color class has a number of members (see Table 11-11) available to get color information from. You can use only three common methods to place color information into a Color structure:

FromArgb() returns a Color class based on the alpha, red, green, and blue values passed to it.

FromKnownColor() returns a Color class based on a predefined color.

FromName() returns a Color class based on the string color name passed.

You must use one of these three methods to create your color because there is no Color constructor.

Table 11-11. Common Color Members

Member

Description

A

Gets the alpha component

B

Gets the blue component

G

Gets the green component

GetBrightness()

Gets the brightness of the color based on the hue-saturation-

 

brightness (HSB) value of the color

GetHue()

Gets the hue of the color, based on the HSB value of the color

GetSaturation()

Gets the saturation of the color, based on the HSB value of the color

IsKnownColor()

true if it is a known color

IsNamedColor()

true if it is a named color

IsSystemColor()

true if it is a system color

Name

Gets the name of a “named” color

R

Gets the red component

ToArgb()

Gets the 32-bit ARGB value of the color

ToKnownColor()

Gets the KnownColor value of the color

 

 

There are two basic methods of defining a Color class: defining it using a combination of red, green, blue, and alpha component values or selecting the color from a list of predefined colors.

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 +

481

Custom Colors

To build your own custom color, you need to use the Color class’s FromArgb() method. There are several overloads of the method, but you will most likely use two of them. The first method takes only the red, green, and blue components and defaults the alpha component to opaque (255). The second method allows you to specify the alpha component.

// Pure red

Color red1 = Color::FromArgb(255, 0, 0);

Color red2 = Color:: FromArgb(255, 255, 0, 0); //Pure green

Color green1 = Color::FromArgb(0, 255, 0); Color green2 = Color::FromArgb(255, 0, 255, 0); //Pure blue

Color blue1 = Color::FromArgb(0, 0, 255); Color blue2 = Color::FromArgb(255, 0, 0, 255);

You can make transparent or semitransparent colors by adjusting the alpha component passed to the FromArgb() method:

Color transparentgray = Color::FromArgb(127, 127, 127, 127);

Named Colors

The Color class provides a large number of predefined, or named, colors. There are two types of named colors. The first is a name that describes the color. These types of colors range (alphabetically) from AliceBlue to YellowGreen. The second type of color uses a name that describes its role in the Windows standard interface, such as ControlText, ScrollBar, and Window.

The three ways of creating named colors are using the FromKnownColor() method, using the static named color method directly, or using the string name of the color.

Color c1 = Color::FromKnownColor(KnownColor::AliceBlue);

Color c2 = Color::AliceBlue;

Color c3 = Color::FromName("AliceBlue");

Pens and Brushes

When you render images to a drawing surface, you need an object to actually do the drawing. GDI+ provides two objects: the Pen and the Brush. The Pen type is used to draw the outline of a shape, and the Brush type fills in an enclosed shape. (Makes sense, don’t you think?)

Pens

You’ve all worked with a pen, so the idea of what a pen does shouldn’t be hard to visualize. Normally, you use a pen to draw the outline of the object. Most likely, you draw a solid line, but sometimes you might use a sequence of a bunch of dots and dashes. When you’re drawing a line between two objects, you probably will put an arrow on one or both ends. If you like variety, you might even use a red or blue pen along with your black one.

The Pen type provided by GDI+ provides basically the same functionality.

482

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 +

Custom Pens

You use the Pen constructor to create a Pen object, and then you use its properties (see Table 11-12) to indicate how you want the Pen used. There are several constructors to create a Pen, but in most cases the simple color and width constructors do the trick:

Pen^ pen1 = gcnew Pen(Color::Blue, 3.0);

Or if you want the Pen to be only 1 graphics unit thick, you could use this even easier code:

Pen^ pen2 = gcnew Pen(Color::Blue);

Notice I used the term “graphics unit.” The Pen type’s thickness is based on the graphics unit, not pixels, though the default is pixels.

Table 11-12. Common Pen Properties

Property

Description

Color

Specifies the color of the Pen

CompoundArray

Specifies the splitting of the width of a line into multiple parallel lines

CustomEndCap

Specifies a custom cap for the end of the line

CustomStartCap

Specifies a custom cap for the start of the line

DashCap

Specifies the dash-dot-space pattern used at the cap of a line

DashOffset

Specifies the distance from the start of the line to the beginning of the

 

dash-dot-space pattern

DashPattern

Specifies a predefined dash-dot-space pattern to be used for a line

DashStyle

Specifies the style of the dash lines

EndCap

Specifies a predefined cap to be used for the end of the line

LineJoin

Specifies the style of the join between two consecutive lines

PenType

Specifies the style of the line generated by the Pen

StartCap

Specifies a predefined cap to be used for the start of the line

Width

Specifies the width of the Pen

 

 

Named Pens

If you are creating a pen that is only 1 graphics unit thick and uses a named color, then you can use one of the pens found in the Pens class. The name of the pen is the same as the name of the named color it is using:

Pen^ pen = Pens::AliceBlue;

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 +

483

System Pens

System pens are virtually the same as named pens, except that instead of a pen being named after a color, it is named after the role that the Pen would use on the Windows GUI interface. Also, you will find system pens in the SystemPens class and not in the Pens class:

Pen^ pen = SystemPens::MenuText;

Listing 11-12 presents an example program that draws a few lines using the CompoundArray,

DashStyle, StartCap, and EndCap properties.

Listing 11-12. Creating Some Random Lines

namespace

DrawingLines

{

 

using

namespace System;

using

namespace System::ComponentModel;

using

namespace System::Collections;

using

namespace System::Windows::Forms;

using

namespace System::Data;

using

namespace System::Drawing;

using

namespace System::Drawing::Drawing2D;

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

{

public:

Form1(void)

{

InitializeComponent();

pen = gcnew array<Pen^>(5);

//a one unit width black pen pen[0] = Pens::Black;

//a one unit with purple pen broken with dashes pen[1] = gcnew Pen(Color::Purple); pen[1]->DashStyle = DashStyle::Dash;

//a 4 unit width chocolate pen

pen[2] = gcnew Pen(Color::Chocolate, 4);

//An 8 width royalblue pen made of three lines narrow wide narrow pen[3] = gcnew Pen(Color::RoyalBlue, 10);

array<float>^ cArray = gcnew array<float> { 0.0f, 0.1f, 0.3f, 0.7f, 0.9f, 1.0f

};

pen[3]->CompoundArray = cArray;

//a 5 width tomato pen with diamond start and round end anchors pen[4] = gcnew Pen(Color::Tomato, 5);

pen[4]->StartCap = LineCap::DiamondAnchor; pen[4]->EndCap = LineCap::RoundAnchor;

}

484

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; array<Pen^>^ pen;

#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"Drawing Some lines"; 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)

{

Random ^rand = gcnew Random();

for (int i = 0; i < 10; i++)

{

e->Graphics->DrawLine(pen[i%5], rand->Next(0,299), rand->Next(0,299), rand->Next(0,299), rand->Next(0,299));

}

}

};

}

Figure 11-12 shows one instance of DrawingLines.exe running. I doubt you will ever see the same combination of lines being displayed twice.

The preceding code is pretty self-explanatory, with the help of the embedded comments, except for two things. The first is that you need to add the System::Drawing::Drawing2D namespace. This namespace defines both the DashStyle and LineCap classes.

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 +

485

Figure 11-12. Displaying random lines

The second is the code that implements the CompoundArray property. This property splits a single line into multiple parallel lines. It does this by taking the width of a line and defining some portions as visible and other portions as not visible. The basic idea is, starting at 0 percent, find the first percent value that the line will be visible and write that into a Single area, and then find the percent where it becomes invisible again and write that value into the area. Repeat the process for all the parallel sublines that make up the full area, stopping at 100 percent.

If you want to define the entire line width as being visible (a waste of time, by the way), the array will look like this:

array<float>^ cArray = gcnew array<float> { 0.0f, 1.0f };

If you want to define the top half of the line as visible and the bottom as invisible (again, a waste of time), the array will look like this:

array<float>^ cArray = gcnew array<float> { 0.0f, 0.5f };

If you want the top 10 percent and the bottom 10 percent only to be visible, the array will look like this:

array<float>^ cArray = gcnew array<float> { 0.0f, 0.1f, 0.9f, 1.0f };

Notice that the compound array always has an even number of elements.

The preceding example breaks the line like this:

So the code ends up looking like this:

486

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 +

pen[3] = gcnew Pen(Color::RoyalBlue, 10); array<float>^ cArray = gcnew array<float> {

0.0f, 0.1f, 0.3f, 0.7f, 0.9f, 1.0f

};

pen[3]->CompoundArray = cArray;

Brushes

You use brushes to fill in the objects that you drew with the pens you defined in the previous section. Unlike the Pen class, the Brush class is an abstract class. You don’t create objects directly from the Brush class; instead, brushes are created from classes derived from the Brush class such as SolidBrush,

HatchBrush, and TextureBrush.

You can also create named brushes and SystemBrushes. The Brushes class will fill a shape like the SolidBrush class. The only difference is that the brushes are predefined with names based on named colors.

Brush^ brush = Brushes::AliceBlue;

SystemBrushes are like the Brushes class, but instead of colors, the SystemBrushes are named based on the Windows role they would represent.

Brush^ brush = SystemBrushes:: ActiveBorder;

SolidBrush, HatchBrush, and TextureBrush are not the only brushes available, but I cover only them to give you some ideas on how to work with brushes.

Solid Brushes

The SolidBrush class is the easiest of the brushes. All it takes in its constructor is the color that you want to fill the shape with. Its only property with any relevance is the color you used in the constructor:

SolidBrush^ brush = gcnew SolidBrush(Color::Black);

Hatch Brushes

The HatchBrush class is a little more complicated than the SolidBrush class. First, you need to add the namespace System::Drawing::Drawing2D so that you can access the both the HatchBrush class and the HatchStyle enumeration. The HatchBrush uses the HatchStyle enumeration (see Table 11-13) to define the look of the brush. GDI+ provides numerous hatch styles.

Table 11-13. Ten of the Many HatchStyle Enumerations

Enumeration

Description

BackwardDiagonal

Specifies a pattern of diagonal lines from the upper right to lower left

Cross

Specifies a pattern of vertical and horizontal lines

DiagonalBrick

Specifies a pattern that looks like slanted bricks

Divots

Specifies a pattern that looks like divots (a golfer’s nightmare)

Horizontal

Specifies a pattern of horizontal lines

Plaid

Specifies a pattern that looks like plaid

SmallConfetti

Specifies a pattern that looks like small confetti

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 +

487

Table 11-13. Ten of the Many HatchStyle Enumerations

Enumeration

Description

Sphere

Specifies a pattern of spheres laid adjacent to each other

Vertical

Specifies a pattern of vertical lines

ZigZag

Specifies a pattern of horizontal lines that looks like zigzags

 

 

The constructor is a little more complicated too, as you need to pass the HatchStyle and two colors, the first being the foreground hatch color and the second being the background color.

using namespace System::Drawing::Drawing2D;

HatchBrush^ b = gcnew HatchBrush(HatchStyle::Divots,

Color::Brown, Color::Green);

Textured Brushes

A TextureBrush class allows you to place an image in the brush and then use it to fill in shapes. The best part of TextureBrush is how little code is needed to get it to work. The basic tasks behind the creation of a TextureBrush are loading the image and then placing it in the brush:

Image^ brushimage = gcnew Bitmap("MyImage.bmp");

TextureBrush^ tbrush = gcnew TextureBrush(brushimage);

Because I haven’t covered images yet, I defer their explanation until later in the chapter. But as you can see in the preceding constructor, once you have an image available, it is a simple process to place it into a TextureBrush.

But that is not where the story ends. What happens if the brush is smaller than the shape it is trying to fill? The TextureBrush provides a WrapMode parameter (see Table 11-14) in the constructor (and also a property) to determine what to do—either clamp it or tile it. Clamping means that only one copy of the image is drawn, and tiling means that the image is repeatedly drawn until the area is filled.

Table 11-14. WrapModes Enumeration

Enumeration

Description

Clamp

Clamp the image to the object boundary

Tile

Tile the shape

TileFlipX

Tile the shape, flipping horizontally on each column

TileFlipXY

Tile the shape, flipping horizontally and vertically

TileFlipY

Tile the shape, flipping vertically on each row

 

 

There is one more piece of the puzzle. The first brush starts in the upper-left corner of the control you are drawing in. Thus, if you are filling a rectangle, for instance, and you want the brush to start in the upper-left corner of the rectangle, then you need to call the Brush class’s TranslateTransform() method to translate the brush to start at that location: