Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1
.pdf488 |
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 + |
//Translate brush to same start location as rectangle tbrush->TranslateTransform(25,25);
//Fill rectangle with brush
g->FillRectangle(tbrush, 25, 25, 250, 250);
Listing 11-13 shows the tiling of the TextureBrush using WrapMode::TileFlipXY. It also shows how to translate the starting point of the tiling to the upper-left corner of the shape you are trying to fill.
Listing 11-13. Filling with a TextureBrush
namespace |
TextureBrushEx |
{ |
|
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();
}
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"Texture Brush"; this->Paint +=
gcnew System::Windows::Forms::PaintEventHandler(this, &Form1::Form1_Paint);
this->ResumeLayout(false);
}
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 + |
489 |
#pragma endregion
private:
System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e)
{
// Load Image
Image^ bimage = gcnew Bitmap("Images\\CLICppCover.gif"); // Create brush
TextureBrush^ tbsh = gcnew TextureBrush(bimage, WrapMode::TileFlipXY);
//Translate brush to same start location as rectangle tbsh->TranslateTransform(25,25);
//Fill rectangle with brush
e->Graphics->FillRectangle(tbsh, 25, 25, 250, 250);
}
};
}
Figure 11-13 shows TextureBrushEx.exe in action. Remember to make sure that the bitmap file is in the Images directory off the current executable starting directory so the program can find it. If it is not, then the program will abort.
Figure 11-13. Displaying the tiled TextureBrush
Rendering Prebuilt Images
If you are implementing GDI+, you are probably planning to do one of two things: Render an existing image or draw your own image. You will cover rendering an existing image first, as it is the easier of the two processes.
Here’s the process in a nutshell. Load the image. Draw the image. That’s it. And it can be done in one line, too!
g->DrawImageUnscaled(Image::FromFile("Images\\CLICppCover.jpg"), 0.0, 0.0);
490 |
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 + |
Of course, if you want a little more control, there is another DrawImage() method that you can work with. The Image class has a few members (see Table 11-15) with which you can manipulate the image.
Table 11-15. Common Image Class Members
Member |
Description |
FromFile() |
Static method to load an image from a file |
FromHbitmap() |
Static method to load a bitmap from a Windows handle |
FromStream() |
Static method to load an image from a stream |
GetBounds() |
Returns a bounding rectangle for the image |
Height |
Specifies the height of the image |
HorizontalResolution |
Specifies the horizontal resolution of the image in pixels per inch |
PhysicalDimensions |
Specifies the size of the image |
RotateFlip() |
Rotates, flips, or rotates and flips the image |
Save() |
Saves the file to a stream |
Size |
Specifies the size of the image |
VerticalResolution |
Specifies the vertical resolution of the image in pixels per inch |
Width |
Specifies the width of the image |
|
|
Before you can render an image, you need to load it from some source, either from a file as shown previously or a data stream (maybe the Internet?). Once the image is loaded, the Image class provides you the ability to flip and rotate the image.
■Note The Image class doesn’t use the GraphicsUnit, as you might expect. Instead, it uses pixels per inch.
Once you have an image, you’re ready to render it. You’ve seen the Graphics class’s DrawImageUnscaled() method. That is about the extent of the functionality it provides. It can take an image and the location where you want to place it. A more flexible rendering method is DrawImage(). It takes myriad overloads (you can examine them at your leisure within the .NET Framework documentation), but the most useful overload takes the image and stretches it to the size you want (see Listing 11-14).
Listing 11-14. Stretching an Image
namespace |
DrawImageEx |
{ |
|
using |
namespace System; |
using |
namespace System::ComponentModel; |
using |
namespace System::Collections; |
using |
namespace System::Windows::Forms; |
using |
namespace System::Data; |
using |
namespace System::Drawing; |
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 + |
491 |
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"Draw Image"; 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)
{
Image^ img = Image::FromFile("Images\\CLICppCover.gif"); e->Graphics->DrawImage(img, 0, 0, img->Width*2, img->Height*2);
}
};
}
Figure 11-14 shows the end result of DrawImageEx.exe, which doubles the image with the DrawImage() method. It is a little blurry but not too bad.
492 |
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-14. Doubling an image’s size
One last note about rendering images. So far you have only loaded images from files of type .gif, but you can actually load .bmp, .jpg, .png, and .tif image files without having to change a single line of code other than the name of the file.
Drawing Your Own Shapes and Lines
Now you can finally get to the fun part of GDI+: drawing your own images. You saw some of this in action earlier in the chapter. The steps involved are quite easy: Grab the Graphics class and then draw or fill the objects you want using the appropriate method. I listed all the methods you will likely use back in Table 11-3, so you might want to take a quick peek back there to refresh your memory.
Because all it really takes to draw an image is calling methods, let’s create a simple piece of artwork with the example in Listing 11-15.
Listing 11-15. A Piece of Art
namespace |
HappyFace |
{ |
|
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 + |
493 |
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(300, 300);
this->Name = L"Form1"; this->Text = L"Happy Face"; 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;
Pen^ b4pen = gcnew Pen(Color::Black, 4);
// Head
Rectangle rect = Drawing::Rectangle(25, 25, 250, 250); g->FillEllipse(Brushes::Yellow, rect); g->DrawEllipse(b4pen, rect);
// Mouth
g->FillPie(Brushes::White, 100, 175, 100, 50, 0, 180); g->DrawPie(b4pen, 100, 175, 100, 50, 0, 180);
// Left Eye
rect = Drawing::Rectangle(100, 100, 25, 25); g->FillEllipse(Brushes::White, rect); g->DrawEllipse(b4pen, rect);
494 |
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 + |
// Right Eye
rect = Drawing::Rectangle(175, 100, 25, 25); g->FillEllipse(Brushes::White, rect); g->DrawEllipse(b4pen, rect);
// Get rid of pen Created delete b4pen;
}
};
}
Figure 11-15 shows the results of HappyFace.exe, which is about the limit of my artistic abilities.
Figure 11-15. A happy face
Advanced GDI+
I kind of like the happy face I created in the last section, so I’ll get a little more mileage out of it by using it to demonstrate a few more advanced GDI+ topics: scrollable windows, optimizing, and double buffering. By “advanced,” I don’t mean difficult—rather, I mean less obvious in how to implement. All three topics aren’t that hard to implement.
Scrollable Windows
In the previous chapter on Win Forms, you didn’t have to worry about a scrolling window as the Win Form handled it itself. With GDI+, on the other hand, it’s up to you to add the necessary two lines in your code to get the scrollable window to work. Yep, you read correctly: two lines of code.
For those of you who aren’t sure what a scrollable window is, it’s a window that automatically attaches scroll bars to itself when the display information extends beyond its width. You use the scroll bar to shift the display area over so you can view this obscured displayed information.
To enable automatic scroll bars in a form, you need to update the AutoScrollMinSize property for the form:
this->AutoScrollMinSize = System::Drawing::Size(400, 400);
The size that you need to specify is the smallest area needed to display all the information. In my case, I was a little overzealous on the size so that you can see the scrolling better.
496 |
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 + |
routines haven’t been notified that the scroll took place. They’re still drawing the same information at the same locations. Thus, the window is just repainting the newly exposed clip area with the original and wrong display information.
You have two (at least) ways of solving this problem. You might try adjusting each of the drawing routines by the amount of the scroll so that when they’re called they render correctly. This solution isn’t so bad when you’re dealing with a handful of drawing and filling routines, but it’s not good for a large number of routines.
An easier solution is to translate the origin of the Graphics class using the TranslateTransform() method (which I discussed earlier) to reflect the scroll. This solution has the same effect as the previous solution. The best part is that you have to add only one line of code, instead of changing every draw and fill routine. (Told you it would take two lines of code!)
g->TranslateTransform((float)AutoScrollPosition.X,(float)AutoScrollPosition.Y);
It’s also fortunate that the Form class provides a property, AutoScrollPosition, which indicates how much was scrolled.
Listing 11-16 shows the happy face program modified to handle scroll bars.
Listing 11-16. A Scrolling Happy Face
namespace |
ScrollingHappyFace |
{ |
|
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();
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 + |
497 |
this->AutoScrollMinSize = System::Drawing::Size(400,400);
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"Scrolling Happy Face"; 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->TranslateTransform((float)AutoScrollPosition.X,
(float)AutoScrollPosition.Y);
Pen^ b4pen = gcnew Pen(Color::Black, 4);
// Head
Rectangle rect = Drawing::Rectangle(25, 25, 250, 250); g->FillEllipse(Brushes::Yellow, rect); g->DrawEllipse(b4pen, rect);
// Mouth
g->FillPie(Brushes::White, 100, 175, 100, 50, 0, 180); g->DrawPie(b4pen, 100, 175, 100, 50, 0, 180);
// Left Eye
rect = Drawing::Rectangle(100, 100, 25, 25); g->FillEllipse(Brushes::White, rect); g->DrawEllipse(b4pen, rect);
// Right Eye
rect = Drawing::Rectangle(175, 100, 25, 25); g->FillEllipse(Brushes::White, rect); g->DrawEllipse(b4pen, rect);
// Get rid of pen Created delete b4pen;
}
};
}
Figure 11-18 shows a happily scrolled happy face.