|  | Click Here!

Search the site:
EXPERT SEARCH

| Programming Languages
| Databases
| Security
| Web Services
| Network Services
| Middleware
| Components
| Operating Systems
| User Interfaces
| Groupware & Collaboration
| Content Management
| Productivity Applications
| Hardware
| Fun & Games

EarthWeb sites
Crossnodes
Datamation
Developer.com
DICE
EarthWeb.com
EarthWeb Direct
ERP Hub
Gamelan
GoCertify.com
HTMLGoodies
Intranet Journal
IT Knowledge
IT Library
JavaGoodies
JARS
JavaScripts.com
open source IT
RoadCoders
Y2K Info

| ## Charlie Calvert's C++ Builder Unleashed ## - 7 - ## Graphics This chapter covers the basics of graphics programming in the VCL. The VCL encapsulates the Windows Graphics Device Interface, or GDI. GDI programming can be a subtle and dangerous process. The VCL tames this technology and makes it extremely easy to use. In the following pages you will learn about: The `TCanvas` object Painting shapes on the screen Working with colors Working with bitmaps Working with metafiles Drawing fractals Working with fonts It's important to understand that the basic graphics functionality presented here is a far cry from the sophisticated tools you find in the DirectX programs seen in Chapter 28, "Game Programming." This is just the basic functionality needed to present standard Windows programs. However, the material presented in this chapter is useful in most standard Windows programs, and it is part of the core knowledge that all VCL programmers should possess. In particular, it shows how the VCL encapsulates and simplifies Windows GDI programming. ### Graphics.Hpp The core of the VCL graphics support is found in `Graphics.Hpp`. The following objects can be found in this file:
Object | Description
`TCanvas` | This is the basic graphics object used to paint shapes on a form or other surface. It is the primary wrapper around the GDI.
`TBrush` | This is the object used to designate the color or pattern that fills in the center of shapes such as rectangles or ellipses.
`TPen` | This is the object used for drawing lines, and as the outline for rectangles or ellipses.
`TPicture` | This is the generalized, high-level VCL wrapper around "pictures" such as bitmaps, metafiles, or icons.
`TMetaFileCanvas` | This is the drawing surface for a metafile.
`TMetaFile` | This is the VCL wrapper around a windows metafile.
`TBitmap` | This is the VCL wrapper around bitmaps.
`TIcon` | This is the VCL wrapper around icons.
`TGraphicsObject` | This is the base class for `TBrush`, `TFont`, and `TPen`.
`TGraphic` | This is the base class for `TMetaFile`, `TBitmap`, and `Ticon`.

Many of these objects are explored in the next few pages. In particular, I demonstrate how to use bitmaps and pens and show how to draw basic geometric shapes to the screen. You also see how to work with bitmaps and metafiles.

After the `TFont` object, the next most important graphics object in the VCL is `TCanvas`. This is a wrapper around the Windows GDI, or Graphics Device Interface. The GDI is the subsystem Windows programmers use to paint pictures and other graphics objects. Most of the content of `Graphics.Hpp` is aimed at finding ways to simplify the GDI so that it is relatively easy to use.

Two other important graphics-based objects not found in `Graphics.Hpp` are `TImage` and `TPaintBox`. Both of these components are covered in this chapter.

### The TColor Type

Almost all the graphics objects use the simple `TColor` type. This is one of the building blocks on which the whole graphics system is laid.

For most practical purposes, a variable of type `TColor` is synonymous with the built in Windows type `COLORREF`. However, the actual declaration for `TColor` looks like this:

    enum TColor {clMin=-0x7fffffff-1, clMax=0x7fffffff};

If you know the `COLORREF` type, you can see that it is similar to the `TColor` type. In a sense, the `TColor` type is nothing but a set of predefined `COLORREF`s.

The Windows palette system enables you to define three different colors, Red, Green, and Blue, where each color has 255 different shades. These colors are specified in the last three bytes of the 4-byte long value used to represent a variable of type `TColor`.

Here is what the three primary colors look like:

    Canvas->Brush->Color = 0x000000FF;  // Red

    Canvas->Brush->Color = 0x0000FF00;  // Green

    Canvas->Brush->Color = 0x00FF0000;  // Blue

You worked with these colors, and combinations of these colors, in the RGBShape program from Chapter 2, "Basic Facts about C++Builder."

Of course, it's not convenient to have to write out these numbers directly in hex. Instead, you can use the RGB macro:

    Canvas->Brush->Color = RGB(255, 0, 0);  // Red

    Canvas->Brush->Color = RGB(0, 255, 0);  // Green

    Canvas->Brush->Color = RGB(0, 0, 255);  // Blue

You can, of course, combine red, green, and blue to produce various shades:

    RGB(255, 0, 255); // Purple

    RGB(255, 255, 0); // Yellow

    RGB(127, 127, 127);  // Gray

The VCL also provides a series of constants you can use to specify common colors. `clBlack`, for instance, has the same internal number you would obtain from the following code:

    COLORREF clBlack = RGB(0, 0, 0);

Here are some declarations from `GRAPHICS.HPP`:

    #define clBlack TColor(0)

    #define clMaroon TColor(128)

    #define clGreen TColor(32768)

    #define clOlive TColor(32896)

If you want to experiment with this system, you can create a new project in BCB, drop a button on the main form and run some changes on a method that looks like this:

    void __fastcall TForm1::Button1Click(TObject *Sender)

    {

      Canvas->Brush->Color = 0x0000FFFF;

      Canvas->Rectangle(0, 0, 200, 200);

    }

This code will draw a bright yellow rectangle in the upper right corner of the main form. Try changing the values of the `Brush` color to `0x00FF0000`, to `RGB(255, 0, 255)`, to `clBlue`, and so on. If you get tired of drawing rectangles, you can switch to ellipses instead:

    Canvas->Ellipse(0, 0, 200, 200);

I talk more about drawing shapes with a canvas later in the chapter.

Most of the time you will pick colors from the `Color` property editor provided by most components. For instance, you can double click the `Color` property for a `TForm` object to bring up a `TColorDialog`. However, there are times when you need to work with the `TColor` type directly, which is why I have explained the topic in some detail.

#### The Canvas Object

All forms, and many components, have a canvas. You can think of a canvas as being the surface on which graphics objects can paint. In short, the metaphor used by the developers here is of a painter's canvas.

This `TCanvas` object is brought into the core component hierarchy through aggregation, which means in many cases that you access its features via a field of an object such as a `TForm` or `TImage` object. For instance, you can write the following:

    Form1->Canvas->TextOut(1, 1, "Hello from the canvas");

This statement writes the words `"Hello from the canvas"` in the upper-left corner of a form. Notice that you do not have to access or reference a device context directly in order to use this method.

Of course, most of the time you are accessing `Form1` from inside of `Form1`, so you can write

    Canvas->TextOut(1, 1, "Hello from the canvas");

However, you cannot write

    Form1->TextOut(1, 1, "Hello from the canvas");

This is because the `Canvas` object is aggregated into `Form1`. `TCanvas` is not brought in through multiple inheritance. Furthermore, the aggregation does not attempt to wrap each of the methods of `TCanvas` inside methods of `TForm`. Aggregation is discussed in more detail in Chapters 19, "Inheritance," and 20, "Encapsulation."

The `Canvas` object has several key methods that all VCL programmers should know:

`Arc` | Draw an arc
`Chord` | A closed figure showing the intersection of a line and an ellipse
`CopyRect` | Copy an area of one canvas to another
`Draw` | Draw a bitmap or other graphic on a canvas
`Ellipse` | Draw an ellipse
`FillRect` | Fill a rectangle
`FloodFill` | Fill an enclosed area
`FrameRect` | Draw a border around a rectangle
`LineTo` | Draw a line
`MoveTo` | Draw a line
`Pie` | Draw a pie-shaped object
`Polygon` | Draw a multisided object
`PolyLine` | Connect a set of points on the canvas
`Rectangle` | Draw a rectangle
`RoundRect` | Draw a rectangle with rounded corners
`StretchDraw` | Same as `Draw`, but stretches the object to fill an area
`TextHeight` | The height of a string in the current font
`TextOut` | Output text
`TextRect` | Output text in a defined area
`TextWidth` | The width of a string in the current font

The following properties of the `Canvas` object are important:

    Font

    Brush

    Pen

    Pixels

The following events can be important to some very technical programmers:

    OnChange

    OnChanging

If you know the Windows API, many of these methods will be familiar to you. The big gain from using the `Canvas` object rather than the raw Windows GDI calls is that the resources you use will be managed for you automatically. In particular, you never need to obtain a device context, nor do you have to select an object into a device context.

In some cases, you will get better performance if you write directly to the Windows GDI. However, it's a mistake to assume that you will always get better by doing so. For instance, the VCL graphics subsystem will cache and share resources in a sophisticated manner that would be very difficult to duplicate in your own code. My personal opinion is that you should use the VCL graphics routines whenever possible, and only turn to the raw Windows API when you run up against an area of graphics not covered by the VCL.

The DrawShapes program demonstrates how easy it is to use the `TCanvas` object in a program. This application delineates the outlines of a simple paint program that has the capability to draw lines, rectangles, and ellipses. A screen shot of the program is shown in Figure 7.1, and the source for the program appears in Listings 7.1 and 7.2.
FIGURE 7.1. The DrawShapes program shows how to create the lineaments of a simple paint program.

Listing 7.1. The header file for the DrawShapes program declares an enumerated type and several simple fields.

    ///////////////////////////////////////

    // Main.cpp

    // DrawShapes Example

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef MainH

    #define MainH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\Menus.hpp>

    #include <vcl\ExtCtrls.hpp>

    #include <vcl\Buttons.hpp>

    #include <vcl\Dialogs.hpp>

    enum TCurrentShape {csLine, csRectangle, csEllipse};

    class TForm1 : public TForm

    {

    __published:

      TMainMenu *MainMenu1;

      TMenuItem *File1;

      TMenuItem *Open1;

      TMenuItem *Save1;

      TMenuItem *N1;

      TMenuItem *Rectangle1;

      TMenuItem *Shapes1;

      TMenuItem *Rectangle2;

      TMenuItem *Ellipse1;

      TMenuItem *Colors1;

      TMenuItem *Brush1;

      TMenuItem *Pen1;

      TPanel *Panel1;

      TSpeedButton *SpeedButton1;

      TSpeedButton *SpeedButton2;

      TSpeedButton *SpeedButton3;

      TSpeedButton *SpeedButton4;

      TMenuItem *Lines1;

      TColorDialog *ColorDialog1;

      TSpeedButton *SpeedButton5;

      void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button,

                                    TShiftState Shift, int X, int Y);

      void __fastcall FormMouseUp(TObject *Sender, TMouseButton Button,

                                  TShiftState Shift, int X, int Y);

      void __fastcall FormMouseMove(TObject *Sender, TShiftState Shift, int X,

                                    int Y);

      void __fastcall SpeedButton1Click(TObject *Sender);

      void __fastcall Brush1Click(TObject *Sender);

      void __fastcall Pen1Click(TObject *Sender);

      void __fastcall SpeedButton5Click(TObject *Sender);

    private:

      TRect FShapeRect;

      TColor FPenColor;

      TColor FBrushColor;

      bool FDrawing;

      TCurrentShape FCurrentShape;

      int FPenThickness;

      void __fastcall DrawShape();

    public:

      virtual __fastcall TForm1(TComponent* Owner);

    };

    extern TForm1 *Form1;

    #endif

###

Listing 7.2. The main form for the DrawShapes program shows how to draw shapes on a canvas.

    ///////////////////////////////////////

    // Main.cpp

    // DrawShapes Example

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

    {

      FDrawing= False;

      FCurrentShape = csRectangle;

      FBrushColor = clBlue;

      FPenColor = clYellow;

      FPenThickness = 1;

    }

    void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,

                                          TShiftState Shift, int X, int Y)

    {

      FShapeRect.Left = X;

      FShapeRect.Top = Y;

      FShapeRect.Right = - 32000;

      FDrawing = True;

    }

    void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,

                                        TShiftState Shift, int X, int Y)

    {

      FDrawing = False;

      FShapeRect.Right = X;

      FShapeRect.Bottom = Y;

      Canvas->Pen->Mode = pmCopy;

      DrawShape();

    }

    void __fastcall TForm1::DrawShape()

    {

      Canvas->Brush->Color = FBrushColor;

      Canvas->Pen->Color = FPenColor;

      Canvas->Pen->Width = FPenThickness;

      switch (FCurrentShape)

      {

        case csLine:

          Canvas->MoveTo(FShapeRect.Left, FShapeRect.Top);

          Canvas->LineTo(FShapeRect.Right, FShapeRect.Bottom);

          break;

        case csRectangle:

          Canvas->Rectangle(FShapeRect.Left, FShapeRect.Top,

                            FShapeRect.Right, FShapeRect.Bottom);

          break;

        case csEllipse:

          Canvas->Ellipse(FShapeRect.Left, FShapeRect.Top,

                          FShapeRect.Right, FShapeRect.Bottom);

          break;

        default:

          ;

      }

    }

    void __fastcall TForm1::FormMouseMove(TObject *Sender,

                                          TShiftState Shift, int X, int Y)

    {

      if (FDrawing)

      {

        Canvas->Pen->Mode = pmNotXor;

        if (FShapeRect.Right != -32000)

          DrawShape();

        FShapeRect.Right = X;

        FShapeRect.Bottom = Y;

        DrawShape();

      }

    }

    void __fastcall TForm1::SpeedButton1Click(TObject *Sender)

    {

      FCurrentShape = TCurrentShape(dynamic_cast<TSpeedButton *>(Sender)->Tag);

    }

    void __fastcall TForm1::Brush1Click(TObject *Sender)

    {

      ColorDialog1->Color = FBrushColor;

      if (ColorDialog1->Execute())

        FBrushColor = ColorDialog1->Color;

    }

    void __fastcall TForm1::Pen1Click(TObject *Sender)

    {

      ColorDialog1->Color = FPenColor;

      if (ColorDialog1->Execute())

        FPenColor = ColorDialog1->Color;

    }

    void __fastcall TForm1::SpeedButton5Click(TObject *Sender)

    {

      FPenThickness = dynamic_cast<TSpeedButton *>(Sender)->Tag;

    }

This program enables you to draw lines, rectangles, and ellipses on the main form of the application. You can select the type of shape you want to work with through the menus or by clicking on speed buttons. The program also lets you set the color of the shapes you want to draw and specify the width and color of the border around the shape.

#### Pens and Brushes

When setting the color of a shape, you need to know that the filled area inside a shape gets set to the color of the current `Brush` for the canvas. The line that forms the border for the shape is controlled by the current `Pen`.

Here is how to set the current color and width of the `Pen`:

    Canvas->Pen->Color = clBlue;

    Canvas->Pen->Width = 5;

If you want to change the color of the `Brush`, you can write the following code:

    Canvas->Brush->Color = clYellow;

I don't work with it in this program, but you can assign various styles to a `Brush`, as defined by the following enumerated type:

    TBrushStyle { bsSolid, bsClear, bsHorizontal, bsVertical,

      bsFDiagonal, bsBDiagonal, bsCross, bsDiagCross };

By default, a `Brush` has the `bsSolid` type.

To set the style of a `Brush`, you would write code that looks like this:

    Canvas->Brush->Style = bsSolid;

`Brush`es also have `Color` and `Bitmap` properties. The `Bitmap` property can be set to a small external bitmap image that defines the pattern for `Brush`.

There is only one method of the `TBrush` object that you would be likely to use. This method is called `Assign`, and it is used when you want to copy the characteristics of one brush to another brush.

`Pen`s have all the same properties as `Brush`es, but they add a `Width` and `Mode` property. The `Width` property defines the width of the `Pen` in pixels, and the `Mode` property defines the type of operation to use when painting the `Pen` to the screen. These logical operations will be discussed further in just a few moments.

#### Rubber Banding

Before reading this section, fire up the DrawShapes program and practice drawing ellipses and rectangles onscreen so you can see how the rubber-band technique works. If for some reason you can't run the DrawShapes program, open up Windows Paint and draw some squares or circles by first clicking the appropriate icon from the Tools menu. Watch the way these programs create an elastic square or circle that you can drag around the desktop. Play with these shapes as you decide what dimensions and locations you want for your geometric figures.

These tools appear to be difficult for a programmer to create, but thanks to the Windows API, the code is relatively trivial. Following are the main steps involved, each of which is explained in-depth later in this section:

  1. When the user clicks the left mouse button, DrawShapes "memorizes" the x and y coordinates of the `WM_LBUTTONDOWN` event.
  2. As the user drags the mouse across the screen with the left button still down, DrawShapes draws a square or circle each time it gets a `WM_MOUSEMOVE` message. Just before painting each new shape, the program blanks out the previous square or circle. The dimensions of the new shape are calculated by combining the coordinates of the original `WM_LBUTTONDOWN` message with the current coordinates passed in the `WM_MOUSEMOVE` message.
  3. When the user generates a `WM_LBUTTONUP` message, DrawShapes paints the final shape in the colors and pen size specified by the user.

Although this description obviously omits some important details, the outlines of the algorithm should take shape in your mind in the form of only a few relatively simple, logical strokes. Things get a bit more complicated when the details are mulled over one by one, but the fundamental steps should be relatively clear.

> NOTE: In the previous numbered list, I give the names of the messages that generate VCL events. For instance, a `WM_LBUTTONDOWN` message generates an `OnMouseDown` event, the `WM_MOUSEMOVE` message generates an `OnMouseMove` event, and so on. I'm referring to the underlying messages because it is a good idea to remember the connection between VCL events and their associated Windows messages.

Zooming in on the details, here's a look at the response to a `WM_LBUTTONDOWN` message:

    void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,

      TShiftState Shift, int X, int Y)

    {

      FShapeRect.Left = X;

      FShapeRect.Top = Y;

      FShapeRect.Right = - 32000;

      FDrawing = True;

    }

`FormMouseDown` saves the location on which the mouse was clicked in the first two fields of a `TRect` structure called `FShapeRect`. `FShapeRect` is declared as a field of `TForm1`. I set the `Right` field of the `TRect` structure to a large negative number so I know that this is the first time that I need to start tracking the user's mouse movements. This is necessary because the very first shape I draw needs to be treated as a special case.

The last thing done in the `FormMouseDown` method is to set a private variable of `TForm1` called `FDrawing` to `True`. This lets the program know that the user has started a drawing operation.

After the left mouse button is pressed, the program picks up all `WM_MOUSEMOVE` messages that come flying into `DrawShape`'s ken:

    void __fastcall TForm1::FormMouseMove(TObject *Sender,

                                          TShiftState Shift, int X, int Y)

    {

      if (FDrawing)

      {

        Canvas->Pen->Mode = pmNotXor;

        if (FShapeRect.Right != -32000)

          DrawShape();

        FShapeRect.Right = X;

        FShapeRect.Bottom = Y;

        DrawShape();

      }

    }

The first line of the function uses one of several possible techniques for checking to see if the left mouse button is down. If the button isn't down, the function ignores the message. If it is down, the function gets the device context, sets the `Pen`'s drawing mode to `pmNotXor`, memorizes the current dimensions of the figure, draws it, and releases the device context.

The `Mode` property of a `TPen` object sets the current drawing mode in a manner similar to the way the last parameter in `BitBlt` sets the current painting mode. You can achieve the same effect by directly calling the Windows API function called `SetROP2`. In this case, `DrawShape` uses the logical `XOR` and `NOT` operations to `blit` the elastic image to the screen. This logical operation is chosen because it paints the old shape directly on top of the original image, thereby effectively erasing each shape:

- If you `XOR` a square to the screen, the square will show up clearly.

- If you `XOR` that same image again in the same location, the image will disappear.

Such are the virtues of simple logical operations in graphics mode.

> NOTE: Aficionados of graphics logic will note that the logical operation employed by `DrawShapes` is a variation on the exclusive `OR` (`XOR`) operation. This variation ensures that the fill in the center of the shape to be drawn won't blot out what's beneath it. The Microsoft documentation explains the difference like this:

>     R2_XOR:        final pixel = pen ^ screen pixel

>     R2_NOTXORPEN : final pixel = ~(pen ^ screen pixel)

> This code tests to see whether the pixels to be `XOR`ed belong to a pen. Don't waste too much time worrying about logical operations and how they work. If they interest you, fine; if they don't, that's okay. The subject matter of this book is programming, not logic.
> If you were working directly in the Windows API, you would work with a constant called `RT_NOTXORPEN` rather than `pmNotXor`. I have to confess that the VCL's tendency to rename constants used by the Windows API is not a very winning trait. Granted, the people in Redmond who came up with many of those identifiers deserve some terrible, nameless, fate, but once the damage had been done it might have been simpler to stick with the original constants. That way people would not have to memorize two sets of identifiers, one for use with the VCL, and the other for use with the Windows API. You cannot use the Windows constants in place of the VCL constants, as the various identifiers do not map down to the same value.
> Despite these objections, I still think it is wise to use the VCL rather than writing directly to the Windows API. The VCL is much safer and much easier to use. The performance from most VCL objects is great, and in many cases it will be better than what most programmers could achieve writing directly to the Windows API.

Notice that `FormMouseMove` calls `DrawShape` twice. The first time, it passes in the dimensions of the old figure that needs to be erased. That means it `XOR`s the same image directly on top of the original image, thereby erasing it. Then `FormMouseMove` records the location of the latest `WM_MOUSEMOVE` message and passes this new information to `DrawShape`, which paints the new image to the screen. This whole process is repeated over and over again (at incredible speeds) until the user lifts the left mouse button.

In the `DrawImage` function, `Metaphor` first checks to see which shape the user has selected and then proceeds to draw that shape to the screen using the current pen and fill color:

    void __fastcall TForm1::DrawShape()

    {

      Canvas->Brush->Color = FBrushColor;

      Canvas->Brush->Style = bsSolid;

      Canvas->Pen->Color = FPenColor;

      Canvas->Pen->Width = FPenThickness;

      switch (FCurrentShape)

      {

        case csLine:

          Canvas->MoveTo(FShapeRect.Left, FShapeRect.Top);

          Canvas->LineTo(FShapeRect.Right, FShapeRect.Bottom);

          break;

        case csRectangle:

          Canvas->Rectangle(FShapeRect.Left, FShapeRect.Top,

                            FShapeRect.Right, FShapeRect.Bottom);

          break;

        case csEllipse:

          Canvas->Ellipse(FShapeRect.Left, FShapeRect.Top,

                          FShapeRect.Right, FShapeRect.Bottom);

          break;

        default:

          ;

      }

    }

This code sets the current `Pen` and `Brush` to the values chosen by the user. It then uses a `switch` statement to select the proper type of shape to draw to the screen. Most of these private variables such as `FPenColor` are set by allowing the user to make selections from the menu. To see exactly how this works, you can study the code of the application.

Notice that when drawing these shapes, there is no need to track the HDC of the current `Canvas`. One of the primary goals of the `TCanvas` object is to completely hide the HDC from the user. I discuss this matter in more depth in the next section of the chapter, "To GDI or not to GDI."

The final step in the whole operation occurs when the user lifts his finger off the mouse:

    void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,

                                        TShiftState Shift, int X, int Y)

    {

      FDrawing = False;

      FShapeRect.Right = X;

      FShapeRect.Bottom = Y;

      Canvas->Pen->Mode = pmCopy;

      DrawShape();

    }

This code performs the following actions:

- A flag is set stating that the user has decided to stop drawing.

- The final dimensions of the shape are recorded.

- The mode for the `Pen` is switched from `pmNotXor` to the default value, which is `pmCopy`.

- The final image is painted to the screen.

The code that paints the final shape takes into account the colors and the pen thickness that the user selected with the menus.

Well, there you have it. That's how you draw shapes to the screen using the rubber-band technique. Overall, if you take one thing at a time, the process isn't too complicated. Just so you can keep those steps clear in your mind, here they are again:

- Remember where the `WM_LBUTTONDOWN` message took place.

- Draw the shape each time you get a `WM_MOUSEMOVE` message.

- Draw the final shape when you get a `WM_LBUTTONUP` message.

That's all there is to it.

#### To GDI or not to GDI

The VCL will never cut you off completely from the underlying Windows API code. If you want to work at the Windows API level, you can do so. In fact, you can often write code that mixes VCL and raw Windows API code.

If you want to access the HDC for a window, you can get at it through the `Handle` property of the canvas:

    MyOldPenHandle = SelectObject(Canvas->Handle, MyPen->Handle);

In this case you are copying the `Handle` of a `Pen` object into the HDC of the `TCanvas` object.

Having free access to the `Handle` of the `TCanvas` object can be useful at times, but the longer I use the VCL, the less inclined I am to use it. The simple truth of the matter is that I now believe that it is best to let an object of some sort handle all chores that require serious housekeeping. This course of action allows me to rely on the object's internal logic to correctly track the resources involved.

If you are not using the VCL, whenever you select something into an HDC, you need to keep track of the resources pumped out of the HDC by the selection. When you are done, you should then copy the old `Handle` back into the HDC. If you accidentally lose track of a resource, you can upset the balance of the entire operating system. Clearly, this type of process is error-prone and best managed by an object that can be debugged once and reused many times. My options, therefore, are to either write the object myself or use the existing code found in the VCL. In most cases, I simply take the simplest course and use the excellent `TCanvas` object provided by the VCL.

When I do decide to manage an interaction with the GDI myself, I often prefer to get hold of my own HDC and ignore the `TCanvas` object altogether. The reason I take this course is simply that the `TCanvas` object will sometimes maintain the `Canvas`'s HDC on its own, and I therefore can't have complete control over what is happening to it.

Here is a simple example of how to use the GDI directly inside a VCL program:

    HDC DC = GetDC(Form1->Handle);

    HFONT OldFont = SelectObject(DC, Canvas->Font->Handle);

    TextOut(DC, 1, 100, "Text", 4);

    SelectObject(DC, OldFont);

    ReleaseDC(Form1->Handle, DC);

If you look at the code shown here, you will see that I get my own DC by calling the Windows API function `GetDC`. I then select a new font into the DC. Notice that I use the VCL `TFont` object. I usually find it easier to manage `Font`s, `Pen`s, and `Brush`es with VCL objects than with raw Windows API code. However, if you want to create your own fonts with raw Windows API code, you are free to do so.

Here is an example of creating a VCL `Font` object from scratch:

    TFont *Font = new TFont();

    Font->Name = "Time New Roman";

    Font->Size = 25;

    Font->Style = TFontStyles() << fsItalic << fsBold;

    HDC DC = GetDC(Form1->Handle);

    HFONT OldFont = SelectObject(DC, Font->Handle);

    TextOut(DC, 1, 100, "Text", 4);

    SelectObject(DC, OldFont);

    ReleaseDC(Form1->Handle, DC);

    delete Font;

This code allocates memory for a `Font` object, assigns some values to its key properties, and then copies the `Handle` of the `Font` object into the current DC. Notice that I still have to save the old `Font` handle and copy it back into the DC when I am done with it. This is the type of operation that I prefer to have handled by an object. When I am done with the example code shown here, I delete the `Font` object I created.

Examples such as the ones you have just seen show that you can use the Windows API by itself inside a VCL graphics operation, or you can combine VCL code with raw GDI code. The course you choose will be dictated by your particular needs. My suggestion, however, is to use the VCL whenever possible, and to fall back on the Windows API only when strictly necessary. The primary reason for this preference is that it is safer to use the VCL than to write directly to the Windows API. It is also easier to use the VCL than the raw Windows API, but that argument has a secondary importance in my mind.

### Using TImage, Saving and Loading Bitmaps

If you work with the DrawShapes program for awhile, you will find that it has several weaknesses that cry out for correction. In particular, the program cannot save an image to disk, and it cannot repaint the current image if you temporarily cover it up with another program.

Fixing these problems turns out to be remarkably simple. The key functionality to add to the program is all bound up in a single component called `TImage`. This control provides you with a `Canvas` on which you can draw, and gives you the ability to convert this `Canvas` into a bitmap.

The BitmapShapes program, shown in Listing 7.3, shows how to proceed in the creation of an updated DrawShapes program that can save files to disk, and can automatically redraw an image if you switch back to the main form from another program.
Listing 7.3. The main module for the BitmapShapes program.

    ///////////////////////////////////////

    // Main.cpp

    // DrawShapes Example

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

    {

      FDrawing= False;

      FCurrentShape = csRectangle;

      FBrushColor = clBlue;

      FPenColor = clYellow;

      FPenThickness = 1;

    }

    void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,

                                          TShiftState Shift, int X, int Y)

    {

      FShapeRect.Left = X;

      FShapeRect.Top = Y;

      FShapeRect.Right = - 32000;

      FDrawing = True;

    }

    void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,

                                        TShiftState Shift, int X, int Y)

    {

      FDrawing = False;

      FShapeRect.Right = X;

      FShapeRect.Bottom = Y;

      Image1->Canvas->Pen->Mode = pmCopy;

      DrawShape();

    }

    void __fastcall TForm1::DrawShape()

    {

      Image1->Canvas->Brush->Color = FBrushColor;

      Image1->Canvas->Brush->Style = bsSolid;

      Image1->Canvas->Pen->Color = FPenColor;

      Image1->Canvas->Pen->Width = FPenThickness;

      switch (FCurrentShape)

      {

        case csLine:

          Image1->Canvas->MoveTo(FShapeRect.Left, FShapeRect.Top);

          Image1->Canvas->LineTo(FShapeRect.Right, FShapeRect.Bottom);

          break;

        case csRectangle:

          Image1->Canvas->Rectangle(FShapeRect.Left, FShapeRect.Top,

            FShapeRect.Right, FShapeRect.Bottom);

          break;

        case csEllipse:

            Image1->Canvas->Ellipse(FShapeRect.Left, FShapeRect.Top,

            FShapeRect.Right, FShapeRect.Bottom);

          break;

        default:

          ;

      }

    }

    void __fastcall TForm1::FormMouseMove(TObject *Sender,

                                          TShiftState Shift, int X, int Y)

    {

      if (FDrawing)

      {

        Image1->Canvas->Pen->Mode = pmNotXor;

        if (FShapeRect.Right != -32000)

          DrawShape();

        FShapeRect.Right = X;

        FShapeRect.Bottom = Y;

        DrawShape();

      }

    }

    void __fastcall TForm1::SpeedButton1Click(TObject *Sender)

    {

      FCurrentShape = TCurrentShape(dynamic_cast<TSpeedButton *>(Sender)->Tag);

    }

    void __fastcall TForm1::Brush1Click(TObject *Sender)

    {

      ColorDialog1->Color = FBrushColor;

      if (ColorDialog1->Execute())

        FBrushColor = ColorDialog1->Color;

    }

    void __fastcall TForm1::Pen1Click(TObject *Sender)

    {

      ColorDialog1->Color = FPenColor;

      if (ColorDialog1->Execute())

        FPenColor = ColorDialog1->Color;

    }

    void __fastcall TForm1::SpeedButton5Click(TObject *Sender)

    {

      FPenThickness = dynamic_cast<TSpeedButton *>(Sender)->Tag;

    }

    void __fastcall TForm1::Save1Click(TObject *Sender)

    {

      if (SaveDialog1->Execute())

      {

        Image1->Picture->SaveToFile(SaveDialog1->FileName);

      }

    }

    void __fastcall TForm1::Open1Click(TObject *Sender)

    {

      if (OpenDialog1->Execute())

      {

        Image1->Picture->LoadFromFile(OpenDialog1->FileName);

      }

    }

    void __fastcall TForm1::Exit1Click(TObject *Sender)

    {

      Close();

    }

The key change from the DrawShapes program is that all of the `Canvas` operations are performed on the `Canvas` of a `TImage` control rather than directly on the `Canvas` of a form:

    Image1->Canvas->MoveTo(FShapeRect.Left, FShapeRect.Top);

    Image1->Canvas->LineTo(FShapeRect.Right, FShapeRect.Bottom);

A `TImage` control is designed explicitly for the type of operations undertaken by this program. In particular, it maintains an internal bitmap into which images are automatically drawn.

When it comes time to save the picture you have created, you can do so with one line of code:

    void __fastcall TForm1::Save1Click(TObject *Sender)

    {

      if (SaveDialog1->Execute())

      {

        Image1->Picture->SaveToFile(SaveDialog1->FileName);

      }

    }

Loading a file into memory from disk is also a simple one-line operation:

    void __fastcall TForm1::Open1Click(TObject *Sender)

    {

      if (OpenDialog1->Execute())

      {

        Image1->Picture->LoadFromFile(OpenDialog1->FileName);

      }

    }

Both of the preceding calls assume that you have dropped a `TOpenDialog` onto the main form of your application. The `TOpenDialog` object is extremely easy to use, so you should have no trouble learning how to use it from the online help.

> NOTE: When I opt not to explain an object such as `TOpenDialog`, my intention is not to ignore the needs of this book's readers, but rather to avoid inserting boring, repetitious information into the book. If you really crave a reference other than the online help for this kind of information, you should check the bookstore for introductory texts that cover this kind of material.

If you want a bit more flexibility, and desire to have a separate `TBitmap` object which you can use for your own purposes, it is easy to create one from the image you created with this program. To proceed, just create a `TBitmap` object, and then use the `Assign` method to copy the picture from the `TImage` control to your bitmap:

    Graphics::TBitmap *Bitmap = new Graphics::TBitmap();

    Bitmap->Assign(Image1->Picture->Graphic);

    Bitmap->SaveToFile(SaveDialog1->FileName);

    delete Bitmap;

In this example I save the bitmap to file and then delete the object when I am through with it. In the context of the current program, this doesn't make a great deal of sense. However, code like this demonstrates some of the functionality of the `TBitmap` object. I qualify the reference to `TBitmap` with `Graphics.Hpp`, because there are two `TBitmap` declarations in the header files included in most BCB programs.

### Working with Metafiles

Bitmaps are very convenient and are often the best way to work with graphic images. However, BCB also lets you work with enhanced metafiles.

Metafiles are collections of shapes drawn into a device context. Internally, they are little more than a list of GDI calls that can be played back on demand to create a picture. Metafiles have two big advantages over bitmaps:

- They are very small. The same image that might take hundreds of KB to save in bitmaps can often be saved in a metafile of just 10 or 20 thousand bytes.

- Because metafiles consist of a series of shapes, you can, at least potentially, iterate through the list of shapes in a picture and edit them one at a time. This can give you a very powerful ability to precisely edit the contents of a picture.

The disadvantage of metafiles is that they must consist only of a series of GDI calls. As a result, you can't easily transform a photograph or a scanned image into a metafile. Metafiles are best defined as a simple way of preserving text, or as a simple means of storing a series of fairly simple shapes such as a line drawing. In the hands of a good artist, however, you can get metafiles to show some fairly powerful images. (See Figure 7.2.)

######

FIGURE 7.2. A grayscale version of a colorful metafile that ships with Microsoft Office shows some of the power of this technology.
There are two types of metafiles available to Windows users. One has the extension `.WMF`, and is primarily designed for use in the 16-bit world. The second type of file has the extension `.EMF`, for Enhanced Metafile. These latter types of files are designed for use in the 32-bit world. I find them much more powerful than their 16-bit cousins.

The code shown in the forthcoming MetaShapes program is designed to work only with enhanced metafiles. BCB will save a file as a regular metafile if you give it an extension of `.WMF`, and it will save it as an enhanced metafile if you give it an extension of `.EMF`. When using BCB, it is best to stick with enhanced metafiles. If you have some `.WMF` files you want to use with BCB, you might want to find a third-party tool that will convert your `.WMF` into an `.EMF`.

The MetaShapes program is very similar to the DrawShapes and BitmapShapes programs. It has the same capabilities as the BitmapShapes program in terms of its capability to save and preserve images, only it works with metafiles rather than bitmaps. Metafiles are many times smaller than standard `.BMP` files.

There is no `TImage` control for metafiles, so you have to do a little more work to make this system function properly. In particular, you have to explicitly open a metafile and then draw directly into it, as shown in Listing 7.4 and 7.5.
Listing 7.4. The header for the MetaShapes program.

    ///////////////////////////////////////

    // Main.h

    // MetaShapes Example

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef MainH

    #define MainH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\Menus.hpp>

    #include <vcl\ExtCtrls.hpp>

    #include <vcl\Buttons.hpp>

    #include <vcl\Dialogs.hpp>

    enum TCurrentShape {csLine, csRectangle, csEllipse};

    class TForm1 : public TForm

    {

    __published:

      TMainMenu *MainMenu1;

      TMenuItem *File1;

      TMenuItem *Open1;

      TMenuItem *Save1;

      TMenuItem *N1;

      TMenuItem *Exit1;

      TMenuItem *Shapes1;

      TMenuItem *Rectangle2;

      TMenuItem *Ellipse1;

      TMenuItem *Colors1;

      TMenuItem *Brush1;

      TMenuItem *Pen1;

      TPanel *Panel1;

      TSpeedButton *SpeedButton1;

      TSpeedButton *SpeedButton2;

      TSpeedButton *SpeedButton3;

      TSpeedButton *SpeedButton4;

      TMenuItem *Lines1;

      TColorDialog *ColorDialog1;

      TSpeedButton *SpeedButton5;

      TMenuItem *New1;

      TPaintBox *PaintBox1;

      TSaveDialog *SaveDialog1;

      TOpenDialog *OpenDialog1;

      void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button,

                                    TShiftState Shift, int X, int Y);

      void __fastcall FormMouseUp(TObject *Sender, TMouseButton Button,

                                  TShiftState Shift, int X, int Y);

      void __fastcall FormMouseMove(TObject *Sender, TShiftState Shift, int X,

                                    int Y);

      void __fastcall SpeedButton1Click(TObject *Sender);

      void __fastcall Brush1Click(TObject *Sender);

      void __fastcall Pen1Click(TObject *Sender);

      void __fastcall SpeedButton5Click(TObject *Sender);

      void __fastcall New1Click(TObject *Sender);

      void __fastcall Exit1Click(TObject *Sender);

      void __fastcall PaintBox1Paint(TObject *Sender);

      void __fastcall FormDestroy(TObject *Sender);

      void __fastcall Save1Click(TObject *Sender);

      void __fastcall Open1Click(TObject *Sender);

    private:

      TRect FShapeRect;

      TColor FPenColor;

      TColor FBrushColor;

      bool FDrawing;

      TCurrentShape FCurrentShape;

      int FPenThickness;

      TMetafile* MyMetafile;

      TMetafileCanvas* MyCanvas;

      void __fastcall DrawShape();

    public:

      virtual __fastcall TForm1(TComponent* Owner);

    };

    extern TForm1 *Form1;

    #endif

Listing 7.5. The MetaShapes program shows how to draw into a metafile and save it to disk.

    ///////////////////////////////////////

    // Main.cpp

    // MetaShapes Example

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

    {

      FDrawing= False;

      FCurrentShape = csRectangle;

      FBrushColor = clBlue;

      FPenColor = clYellow;

      FPenThickness = 1;

      MyMetafile = new TMetafile;

      MyCanvas = new TMetafileCanvas(MyMetafile, PaintBox1->Canvas->Handle);

    }

    void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,

                                          TShiftState Shift, int X, int Y)

    {

      FShapeRect.Left = X;

      FShapeRect.Top = Y;

      FShapeRect.Right = - 32000;

      FDrawing = True;

    }

    void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,

                                        TShiftState Shift, int X, int Y)

    {

      if (FDrawing)

      {

        FDrawing = False;

        FShapeRect.Right = X;

        FShapeRect.Bottom = Y;

        PaintBox1->Canvas->Pen->Mode = pmCopy;

        MyCanvas->Pen->Mode = pmCopy;

        DrawShape();

      }

    }

    void __fastcall TForm1::DrawShape()

    {

      PaintBox1->Canvas->Brush->Color = FBrushColor;

      PaintBox1->Canvas->Brush->Style = bsSolid;

      PaintBox1->Canvas->Pen->Color = FPenColor;

      PaintBox1->Canvas->Pen->Width = FPenThickness;

      MyCanvas->Brush->Color = FBrushColor;

      MyCanvas->Brush->Style = bsSolid;

      MyCanvas->Pen->Color = FPenColor;

      MyCanvas->Pen->Width = FPenThickness;

      switch (FCurrentShape)

      {

        case csLine:

          MyCanvas->MoveTo(FShapeRect.Left, FShapeRect.Top);

          MyCanvas->LineTo(FShapeRect.Right, FShapeRect.Bottom);

          PaintBox1->Canvas->MoveTo(FShapeRect.Left, FShapeRect.Top);

          PaintBox1->Canvas->LineTo(FShapeRect.Right, FShapeRect.Bottom);

          break;

        case csRectangle:

          PaintBox1->Canvas->Rectangle(FShapeRect.Left, FShapeRect.Top,

                                       FShapeRect.Right, FShapeRect.Bottom);

          MyCanvas->Rectangle(FShapeRect.Left, FShapeRect.Top,

                              FShapeRect.Right, FShapeRect.Bottom);

          break;

        case csEllipse:

          PaintBox1->Canvas->Ellipse(FShapeRect.Left, FShapeRect.Top,

                                     FShapeRect.Right, FShapeRect.Bottom);

          MyCanvas->Ellipse(FShapeRect.Left, FShapeRect.Top,

                            FShapeRect.Right, FShapeRect.Bottom);

          break;

        default:

          ;

      }

    }

    void __fastcall TForm1::FormMouseMove(TObject *Sender,

                                          TShiftState Shift, int X, int Y)

    {

      if (FDrawing)

      {

        PaintBox1->Canvas->Pen->Mode = pmNotXor;

        MyCanvas->Pen->Mode = pmNotXor;

        if (FShapeRect.Right != -32000)

          DrawShape();

        FShapeRect.Right = X;

        FShapeRect.Bottom = Y;

        DrawShape();

      }

    }

    void __fastcall TForm1::SpeedButton1Click(TObject *Sender)

    {

      FCurrentShape = TCurrentShape(dynamic_cast<TSpeedButton *>(Sender)->Tag);

    }

    void __fastcall TForm1::Brush1Click(TObject *Sender)

    {

      ColorDialog1->Color = FBrushColor;

      if (ColorDialog1->Execute())

        FBrushColor = ColorDialog1->Color;

    }

    void __fastcall TForm1::Pen1Click(TObject *Sender)

    {

      ColorDialog1->Color = FPenColor;

      if (ColorDialog1->Execute())

        FPenColor = ColorDialog1->Color;

    }

    void __fastcall TForm1::SpeedButton5Click(TObject *Sender)

    {

      FPenThickness = dynamic_cast<TSpeedButton *>(Sender)->Tag;

    }

    void __fastcall TForm1::New1Click(TObject *Sender)

    {

      delete MyCanvas;

      delete MyMetafile;

      MyMetafile = new TMetafile;

      MyCanvas = new TMetafileCanvas(MyMetafile, 0);

      InvalidateRect(Handle, NULL, True);

    }

    void __fastcall TForm1::PaintBox1Paint(TObject *Sender)

    {

      delete MyCanvas;

      if (MyMetafile)

        PaintBox1->Canvas->Draw(0,0,MyMetafile);

      MyCanvas = new TMetafileCanvas(MyMetafile, PaintBox1->Canvas->Handle);

      MyCanvas->Draw(0, 0, MyMetafile);

    }

    void __fastcall TForm1::FormDestroy(TObject *Sender)

    {

      delete MyCanvas;

    }

    void __fastcall TForm1::Exit1Click(TObject *Sender)

    {

      Close();

    }

    void __fastcall TForm1::Save1Click(TObject *Sender)

    {

      if (SaveDialog1->Execute())

      {

        delete MyCanvas;

        MyMetafile->SaveToFile(SaveDialog1->FileName);

        MyCanvas = new TMetafileCanvas(MyMetafile, 0);

        MyCanvas->Draw(0, 0, MyMetafile);

      }

    }

    void __fastcall TForm1::Open1Click(TObject *Sender)

    {

      if (OpenDialog1->Execute())

      {

        FShapeRect = Rect(0, 0, 0, 0);

        delete MyCanvas;

        TFileStream *Stream = new TFileStream(OpenDialog1->FileName, fmOpenRead);

        MyMetafile->LoadFromStream(Stream);

        MyCanvas = new TMetafileCanvas(MyMetafile, PaintBox1->Canvas->Handle);

        MyCanvas->Draw(0, 0, MyMetafile);

        PaintBox1->Canvas->Draw(0, 0, MyMetafile);

        Stream->Free();

      }

    }

This program works just like the BitmapShapes program shown previously, in that it lets you draw colored shapes to the screen and then save them to file. A screenshot of the program is shown in Figure 7.3.
FIGURE 7.3. The MetaShapes program in action.

When working with metafiles, you need to create both a `TMetafile` object and a `TMetafileCanvas` object.

    MyMetafile = new TMetafile;

    MyCanvas = new TMetafileCanvas(MyMetafile, PaintBox1->Canvas->Handle);

Notice that the `TMetafileCanvas` is explicitly associated with a particular `TMetafile` object. Specifically, the `TMetaFile` object is passed in as the first parameter of the `TMetaFileCanvas` object's constructor. These two classes are designed to work in tandem, and both pieces must be present if you want to create a metafile.

Here is a simple example of how to draw into a `TMetafile` canvas:

    MyCanvas->Ellipse(FShapeRect.Left, FShapeRect.Top,

                      FShapeRect.Right, FShapeRect.Bottom);

This code looks exactly like any other call to the `TCanvas::Ellipse`, only this time I am writing into a `TMetafileCanvas` rather than a form's `Canvas`.

> NOTE: The VCL preserves the `Canvas` metaphor in a wide variety of contexts, such as when you are working with metafiles or bitmaps. This enables you to learn one set of commands, and to then apply them to a wide variety of objects. As you will see later in the book, this type of functionality derives in part from a judicious and intelligent use of polymorphism.
> Basing a suite of objects on one model enables users to quickly get up to speed on new technologies. The underlying code for creating bitmaps is very different from the code for creating metafiles. But the `TCanvas` object enables you to treat each operation as if it were nearly identical. People who are interested in object design should contemplate the elegance of this implementation.

If you only want to display a metafile, you can work solely with the `TMetafile` object. However, if you want to create metafiles, to draw images into a metafile, you need a `TMetafileCanvas` object. You can draw directly into a `TMetafileCanvas`, but when you want to display the image to the user, you need to delete the object so that its contents will be transferred into a metafile that can be displayed for the user:

    delete MyCanvas;

    PaintBox1->Canvas->Draw(0,0,MyMetafile);

In this code I am using a `PaintBox` as the surface on which to display the metafile. The code first deletes the `TMetafileCanvas`, thereby transferring the painting from the `TMetafileCanvas` to the `TMetafile`. This implementation is, I suppose, a bit awkward, but once you understand the principle involved, it should not cause you any serious difficulty.

There is no particular connection between metafiles and the `TPaintBox` object. In fact, I could just as easily have painted directly into a `TForm`. The reason I chose `TPaintBox` is that it enables me to easily define a subsection of a form that can be used as a surface on which to paint. In particular, part of the form in the `MetaShapes` program is covered by a `TPanel` object. To make sure that the user can see the entire canvas on which he will be painting, I used a `TPaintBox`.

If you are interested in these matters, you might want to open up `EXTCTRLS.HPP` and compare the declarations for `TPaintBox` and `TImage`. Both of these controls are descendants of `TGraphicControl`, and both offer similar functionality. The big difference between them is that `TImage` has an underlying bitmap, while `TPaintBox` has a simpler, sparer architecture.

By now it has probably occurred to you that there is no simple means for displaying a metafile to the user at the time it is being created. In particular, you can't show it to the user without first deleting the `TMetafileCanvas`. To avoid performing this action too often, I simply record the user's motions into both the `TMetafileCanvas` and the `Canvas` for the main form:

    PaintBox1->Canvas->Brush->Color = FBrushColor;

     ... // Code ommitted

    MyCanvas->Brush->Color = FBrushColor;

     ...// Code ommitted

    switch (FCurrentShape)

    {

      case csLine:

        MyCanvas->MoveTo(FShapeRect.Left, FShapeRect.Top);

        MyCanvas->LineTo(FShapeRect.Right, FShapeRect.Bottom);

        PaintBox1->Canvas->MoveTo(FShapeRect.Left, FShapeRect.Top);

        PaintBox1->Canvas->LineTo(FShapeRect.Right, FShapeRect.Bottom);

        break;

       ... // etc

This duplication of code, while bothersome, is not really terribly costly in terms of what it is adding to the size of my executable. Needless to say, I use this technique so that the user can see what he or she is painting.

If the user flips away from the program and covers it with another application, I need to repaint the image when the user flips back to `MetaShapes`:

    void __fastcall TForm1::PaintBox1Paint(TObject *Sender)

    {

      delete MyCanvas;

      PaintBox1->Canvas->Draw(0,0,MyMetafile);

      MyCanvas = new TMetafileCanvas(MyMetafile, PaintBox1->Canvas->Handle);

      MyCanvas->Draw(0, 0, MyMetafile);

    }

As you can see, this code deletes the `TMetafileCanvas` and then paints the current image to the screen. I then create a new `TMetafileCanvas` and paint the contents of the current metafile into it:

    MyCanvas->Draw(0, 0, MyMetafile);

This same process occurs when I load a metafile from disk:

    void __fastcall TForm1::Open1Click(TObject *Sender)

    {

      if (OpenDialog1->Execute())

      {

        delete MyCanvas;

        TFileStream *Stream = new TFileStream(OpenDialog1->FileName, fmOpenRead);

        MyMetafile->LoadFromStream(Stream);

        MyCanvas = new TMetafileCanvas(MyMetafile, PaintBox1->Canvas->Handle);

        MyCanvas->Draw(0, 0, MyMetafile);

        PaintBox1->Canvas->Draw(0, 0, MyMetafile);

      }

    }

In this code, I delete the contents of any current `TMetafileCanvas`, not because I want to draw it, but because I want to create a new, blank surface on which to paint. I then load a metafile from disk and create a new `TMetafileCanvas` into which I can paint the contents of the freshly loaded metafile:

    MyCanvas->Draw(0, 0, MyMetafile);

Finally, the program plays the contents of the metafile back to the user:

    PaintBox1->Canvas->Draw(0, 0, MyMetafile);

When you are playing back fairly large metafiles, you can sometimes see the shapes being painted to the screen one at a time. This helps to illustrate the fact that metafiles are literally just lists of GDI calls with their related parameters and device contexts carefully preserved. There are calls available that enable you to walk through these lists and delete or modify individual items. However, I do not take the code in this program quite that far. If you master this process, however, you have the rudiments of a CAD or sophisticated drawing program on your hands.

The only thing left to discuss is saving metafiles to disk, which turns out to be a simple process:

    void __fastcall TForm1::Save1Click(TObject *Sender)

    {

      if (SaveDialog1->Execute())

      {

        delete MyCanvas;

        MyMetafile->SaveToFile(SaveDialog1->FileName);

        MyCanvas = new TMetafileCanvas(MyMetafile, 0);

        MyCanvas->Draw(0, 0, MyMetafile);

      }

    }

This code deletes the current `TMetafileCanvas`, thereby assuring that the `TMetafile` object is up-to-date. It then makes a call to the `TMetafile::SaveToFile` method. Finally, it creates a new `TMetafileCanvas` and draws the contents of the current metafile into it, which enables the user to continue editing the picture after the save.

That is all I'm going to say about metafiles. Admittedly, this is a somewhat esoteric subject from the point of view of many programmers. However, metafiles are a useful tool that can be a great help if you want to save text, or a series of images, in a small, compact file.

When I talk about saving text to a metafile, remember that all calls to draw text on the screen are simply calls into the Windows GDI. You can therefore save these calls in a metafile. Many fancy programs that enable you to quickly scroll through large volumes of text are using metafiles as part of their underlying technology.

### Working with Fonts

The raw Windows API code for working with fonts is quite complex. The VCL, however, makes fonts easy to use.

You have already seen an example of using the `TFont` object. The basic idea is simply that a `TFont` object has eight key properties called `Color`, `Handle`, `Height`, `Name`, `Pitch`, `PixelsPerInch`, `Size`, and `Style`. It is a simple matter to create a `TFont` object and change its properties:

    TFont *MyFont = new TFont();

    MyFont->Name = "BookwomanSH";

    MyFont->Size = 24;

    MyFont->Style = TFontStyles() << fsBold;

    Form1->Font = MyFont;

In this case, the programmer has no obligation to delete the `TFont` object, because its ownership is assumed by the form in the last line. If you did not pass the ownership of the object over to the `Form`, you would need to explicitly delete the `Font` when you were through with it:

    delete MyFont;

Most of the time, if you want to give the user the capability to select a font at runtime, you should use the `TFontDialog` from the dialogs page of the Component Palette. However, you can iterate through all the fonts on your system by using the Fonts field of the `TScreen` object.

The `TScreen` object is used primarily for keeping track of the active forms in your application and the current cursor. However, you can also use it for iterating through all the available fonts on the system. These fonts are kept in a `TStringList` object that is accessible through a property called `Fonts`:

    ListBox1->Items = Screen->Fonts;

The screen object is created automatically by the VCL, and is available to all applications as a global object.

Besides the screen fonts, some programmers might also be interested in accessing the available printer fonts. These can be accessed through an object called `TPrinter`, which is kept in a module called `PRINTERS.HPP`:

      TPrinter *Printer = new TPrinter();

      ListBox2->Items = Printer->Fonts;

      delete Printer;

The BCBFonts program from this book's CD-ROM gives a simple example of how to iterate through all the fonts on the system so the user can pick and choose from them. The source code for the program is shown in Listing 7.6.
Listing 7.6. The main module for the BCBFonts program shows how to view a list of the currently available fonts.

    ///////////////////////////////////////

    // Main.cpp

    // Project: BCBFonts

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #include <vcl\printers.hpp>

    #pragma hdrstop

    #include "Main.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

    {

      ListBox1->Items = Screen->Fonts;

      TPrinter *Printer = new TPrinter();

      ListBox2->Items = Printer->Fonts;

      delete Printer;

      Panel4->Font->Size = TrackBar1->Position;

      ListBox1->ItemIndex = 0;

      Panel4->Caption = ListBox1->Items->Strings[0];

    }

    void __fastcall TForm1::ListBox1Click(TObject *Sender)

    {

      if (ListBox1->ItemIndex != -1)

      {

        Panel4->Font->Name = ListBox1->Items->Strings[ListBox1->ItemIndex];

        Panel4->Caption = Panel4->Font->Name;

      }

    }

    void __fastcall TForm1::TrackBar1Change(TObject *Sender)

    {

      Panel4->Font->Size = TrackBar1->Position;

    }

This program has two list boxes. At program startup, the first list box is initialized to display all the fonts currently available on the system. The right-hand list box shows all the fonts currently available on the printer. As a rule, you don't need to worry about coordinating the two sets of fonts, because Windows will handle that problem for you automatically. However, if you are having trouble getting the right fonts on your printer when working with a particular document, you might want to compare these two lists and see if any obvious problems meet the eye.

If you select a font from the first list box in the BCBFonts program, its name will appear in the caption of a `TPanel` object shown at the bottom of the program. The `Caption` for the panel will use the font whose name is currently being displayed, as shown in Figure 7.4. At the bottom of the BCBFonts program is the `TTrackBar` object, which enables you to change the size of the current font.
FIGURE 7.4. The BCBFonts program gives samples of all the fonts on the system.

###

### Working with Fractals

The graphics code shown so far in this section has been fairly trivial. The last program I show in this chapter ups the ante a bit by showing how you can create fractals, using the VCL graphics objects. This code should make it clear that the VCL tools are quite powerful, and can be put to good use even in fairly graphics-intensive programs.

The sample program on the CD, Fractals, is where you can find all of the code discussed in this section of the chapter. The source for the program is shown in Listings 7.7 through 7.13.
Listing 7.7. This program shows how to use fractals to draw various shapes.

    ///////////////////////////////////////

    // Main.cpp

    // Project: Fractals

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #include "Ferns.h"

    #include "Squares1.h"

    #include "Mandy.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

    }

    //--------------------------------------------------------------------------

    void __fastcall TForm1::Ferns1Click(TObject *Sender)

    {

      FernForm->ShowModal();

    }

    void __fastcall TForm1::Squares1Click(TObject *Sender)

    {

      SquaresForm = new TSquaresForm(this);

      SquaresForm->ShowModal();

      SquaresForm->Free();

    }

    void __fastcall TForm1::Mandelbrot1Click(TObject *Sender)

    {

      MandelForm = new TMandelForm(this);

      MandelForm->ShowModal();

      delete MandelForm;

    }

    //--------------------------------------------------------------------------

###

Listing 7.8. This header is for a form that draws a fractal fern.

    ///////////////////////////////////////

    // Ferns.h

    // Project: Fractals

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef FernsH

    #define FernsH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\ExtCtrls.hpp>

    class TFernForm : public TForm

    {

    __published:

      TTimer *Timer1;

      void __fastcall Timer1Timer(TObject *Sender);

      void __fastcall FormResize(TObject *Sender);

    private:

      void DoPaint();

      float  x, y;

      int MaxIterations;

      int Count;

      int MaxX;

      int MaxY;

    public:

      virtual __fastcall TFernForm(TComponent* Owner);

    };

    extern TFernForm *FernForm;

    #endif

###

Listing 7.9. This form draws a fractal that looks like a fern.

    ///////////////////////////////////////

    // Ferns.cpp

    // Project: Fractals

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Ferns.h"

    #pragma resource "*.dfm"

    TFernForm *FernForm;

    typedef float TDAry[4];

    TDAry a = {0, 0.85, 0.2, -0.15};

    TDAry b = {0, 0.04, -0.26, 0.28};

    TDAry c = {0, -0.04, 0.23, 0.26};

    TDAry d = {0.16, 0.85, 0.22, 0.24};

    TDAry e = {0, 0, 0, 0};

    TDAry f = {0, 1.6, 1.6, 0.44};

    __fastcall TFernForm::TFernForm(TComponent* Owner)

      : TForm(Owner)

    {

      Count = 0;

      x = 0;

      y = 0;

    }

    void TFernForm::DoPaint()

    {

      int k;

      float TempX, TempY;

      k = random(100);

      if ((k > 0) && (k <= 85))

        k = 1;

      if ((k > 85) && (k <= 92))

        k = 2;

      if (k > 92)

        k = 3;

      TempX = a[k] * x + b[k] * y + e[k];

      TempY = c[k] * x + d[k] * y + f[k];

      x = TempX;

      y = TempY;

      if ((Count >= MaxIterations) || (Count != 0))

       Canvas->Pixels[(x * MaxY / 11 + MaxX / 2)]

                     [(y * - MaxY / 11 + MaxY)] =  clGreen;

      Count = Count + 1;

    }

    void __fastcall TFernForm::Timer1Timer(TObject *Sender)

    {

      int i;

      if (Count > MaxIterations)

      {

        Invalidate();

        Count = 0;

      }

      for (i = 0; i <= 200; i++)

        DoPaint();

    }

    void __fastcall TFernForm::FormResize(TObject *Sender)

    {

      MaxX = Width;

      MaxY = Height;

      MaxIterations = MaxY * 50;

Listing 7.10. The header for the Squares form.

    ///////////////////////////////////////

    // Squares.h

    // Project: Fractals

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef Squares1H

    #define Squares1H

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\ExtCtrls.hpp>

    #define BoxCount 25

    class TSquaresForm : public TForm

    {

    __published:

      TTimer *Timer1;

      void __fastcall Timer1Timer(TObject *Sender);

      void __fastcall FormShow(TObject *Sender);

      void __fastcall FormHide(TObject *Sender);

    private:

      void DrawSquare(float Scale, int Theta);

    public:

      virtual __fastcall TSquaresForm(TComponent* Owner);

    };

    extern TSquaresForm *SquaresForm;

    #endif

###

Listing 7.11. A form that draws a hypnotic series of squares.

    ///////////////////////////////////////

    // Squares.cpp

    // Project: Fractals

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #include <math.h>

    #pragma hdrstop

    #include "Squares1.h"

    #pragma resource "*.dfm"

    TColor Colors[BoxCount];

    typedef TPoint TSquarePoints[5];

    TSquarePoints Square =

        {{-100, -100},{100, -100},{100, 100},

         {-100, 100},{-100, -100}};

    TSquaresForm *SquaresForm;

    __fastcall TSquaresForm::TSquaresForm(TComponent* Owner)

      : TForm(Owner)

    {

      int X;

      Randomize;

      Colors[1] = TColor(RGB(random(255), random(255), random(255)));

      for (X = 2; X <= BoxCount; X++)

        Colors[X] = TColor(Colors[X-1] + RGB(random(64), random(64), random(64)));

    }

    void TSquaresForm::DrawSquare(float Scale, int Theta)

    {

      int i;

      float CosTheta, SinTheta;

      TSquarePoints Path;

      CosTheta = Scale * cos(Theta * M_PI / 180);  // precalculate rotation & scaling

      SinTheta = Scale * sin(Theta * M_PI / 180);

      for (i = 0; i <= 4; i++)

      {

        Path[i].x = (Square[i].x * CosTheta +  Square[i].y * SinTheta);

        Path[i].y = (Square[i].y * CosTheta -  Square[i].x * SinTheta);

      }

      Canvas->Polyline(Path, 4);

    }

    void __fastcall TSquaresForm::Timer1Timer(TObject *Sender)

    {

      int i;

      float Scale = 1.0;

      int Theta = 0;

      SetViewportOrgEx(Canvas->Handle, ClientWidth / 2, ClientHeight / 2, NULL);

      Canvas->Pen->Color = clWhite;

      for (i = 1; i <= BoxCount; i++)

      {

        DrawSquare(Scale, Theta);

        Theta = Theta + 10;

        Scale = Scale * 0.85;

        Canvas->Pen->Color = Colors[i];

      }

      for (i = BoxCount - 1; i >= 1; i--)

        Colors[i] = Colors[i - 1];

      Colors[0] = TColor(RGB(Colors[0] + random(64),

                    Colors[0] + random(64),

                    Colors[0] + random(64)));

    }

    void __fastcall TSquaresForm::FormShow(TObject *Sender)

    {

      Timer1->Enabled = True;

    }

    void __fastcall TSquaresForm::FormHide(TObject *Sender)

    {

      Timer1->Enabled = False;

    }

Listing 7.12. The header for the form that draws the Mandelbrot set.

    ///////////////////////////////////////

    // Mandy.h

    // Project: Fractals

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef MandyH

    #define MandyH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\ExtCtrls.hpp>

    class TMandelForm : public TForm

    {

    __published:

      TTimer *Timer1;

      void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button,

      TShiftState Shift, int X, int Y);

      void __fastcall FormMouseUp(TObject *Sender, TMouseButton Button,

      TShiftState Shift, int X, int Y);

      void __fastcall FormResize(TObject *Sender);

      void __fastcall Timer1Timer(TObject *Sender);

      void __fastcall FormDblClick(TObject *Sender);

      void __fastcall FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y);

    private:

      int FDepth;

      float FXRange;   // The width and height of the

      float FYRange;   // mandlebrot plane. Starts at 3.

      int FScrOrgX;

      int FScrOrgY;

      int FScrMaxX;

      int FScrMaxY;

      float FBaseOrgX;

      float FBaseOrgY;

      bool FQuitDrawing;

      void GetOriginsAndWidths(float &XOrg, float &YOrg, float &XMax, float &YMax);

      float Distance(float X, float Y);

      void Calculate(float X, float Y, float &XIter, float &YIter);

      int GetColor(int Steps);

      TRect FShapeRect;

      bool FDrawing;

      void SetBoundary(int ScrX, int ScrY, int ScrX1, int ScrY1);

      void SetMouseDownPos(int ScrX, int ScrY);

      void SetMouseUpPos(int ScrX1, int ScrY1);

      bool Run();

    public:        // User declarations

      virtual __fastcall TMandelForm(TComponent* Owner);

      void __fastcall DrawShape();

    };

    extern TMandelForm *MandelForm;

    #endif

Listing 7.13. A simple form for drawing the Mandelbrot set.

    ///////////////////////////////////////

    // Mandy.cpp

    // Project: Fractals

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #include <math.h>

    #pragma hdrstop

    #include "Mandy.h"

    #pragma resource "*.dfm"

    TMandelForm *MandelForm;

    __fastcall TMandelForm::TMandelForm(TComponent* Owner)

      : TForm(Owner)

    {

      FXRange = 3;

      FYRange = 3;

      FBaseOrgX = -2.25;

      FBaseOrgY = -1.5;

      Width = 550;

      Height = 400;

      FDrawing = False;

    }

    void TMandelForm::GetOriginsAndWidths(float &XOrg, float &YOrg, float &XMax, float                                       &YMax)

    {

      float VOrgX, VOrgY, VMaxX, VMaxY;

      float XPercent, YPercent;

      float MaxXPercent, MaxYPercent;

      VOrgX = FScrOrgX;

      VOrgY = FScrOrgY;

      VMaxX = FScrMaxX;

      VMaxY = FScrMaxY;

      XPercent = VOrgX / Width;

      YPercent = VOrgY / Height;

      MaxXPercent = VMaxX / Width;

      MaxYPercent = VMaxY / Height;

      XOrg = (XPercent * FXRange) + FBaseOrgX;

      YOrg = (YPercent * FYRange) + FBaseOrgY;

      XMax = (MaxXPercent * FXRange) + FBaseOrgX;

      YMax = (MaxYPercent * FYRange) + FBaseOrgY;

      FBaseOrgX = XOrg;

      FBaseOrgY = YOrg;

      FXRange = XMax - XOrg;

      FYRange = YMax - YOrg;

    }

    float TMandelForm::Distance(float X, float Y)

    {

      if ((X != 0.0) && (Y != 0.0))

        return sqrt(pow(X, 2) + pow(Y, 2));

      else

        if (X == 0.0)

          return abs(Y);

        else

          return abs(X);

    };

    void TMandelForm::Calculate(float X, float Y,

                                float &XIter, float &YIter)

    {

      float XTemp, YTemp;

      XTemp = pow(XIter, 2) - pow(YIter, 2) + X;

      YTemp = 2 * (XIter * YIter) + Y;

      XIter = XTemp;

      YIter = YTemp;

    }

    // Steps won't be larger than FDepth.

    int TMandelForm::GetColor(int Steps)

    {

      int TopVal= 16777215; // RGB(255,255,255)

      float Variation;

      int Val;

      Variation = TopVal / FDepth;

      Val = Variation * Steps;

      return Val;

    }

    void TMandelForm::SetBoundary(int ScrX, int ScrY, int ScrX1, int ScrY1)

    {

      FScrOrgX = ScrX;

      FScrOrgY = ScrY;

      FScrMaxX = ScrX1;

      FScrMaxY = ScrY1;

    };

    void TMandelForm::SetMouseDownPos(int ScrX, int ScrY)

    {

      FScrOrgX = ScrX;

      FScrOrgY = ScrY;

    }

    void TMandelForm::SetMouseUpPos(int ScrX1, int ScrY1)

    {

      FScrMaxX = ScrX1;

      FScrMaxY = ScrY1;

      if ((FScrMaxX - FScrOrgX) > 10)

        Run();

    }

    void __fastcall TMandelForm::DrawShape()

    {

      Canvas->Rectangle(FShapeRect.Left, FShapeRect.Top,

        FShapeRect.Right, FShapeRect.Bottom);

    }

    void __fastcall TMandelForm::FormMouseDown(TObject *Sender, TMouseButton Button,

                                               TShiftState Shift, int X, int Y)

    {

      if (Shift.Contains(ssRight))

        FQuitDrawing = True;

      else

        SetMouseDownPos(X, Y);

      FShapeRect.Left = X;

      FShapeRect.Top = Y;

      FShapeRect.Right = - 32000;

      FDrawing = True;

    }

    void __fastcall TMandelForm::FormMouseUp(TObject *Sender, TMouseButton Button,

                                             TShiftState Shift, int X, int Y)

    {

      FDrawing = False;

      FShapeRect.Right = X;

      FShapeRect.Bottom = Y;

      Canvas->Pen->Mode = pmCopy;

      DrawShape();

      SetMouseUpPos(X, Y);

    }

    bool TMandelForm::Run()

    {

      int i, j, Steps;

      float XStep, YStep, XPos, YPos, XOrg, YOrg;

      float XMax, YMax, XIter, YIter;

      bool Done;

      if (FDepth < 1)

        FDepth = 50;

      InvalidateRect(Handle, NULL, True);

      GetOriginsAndWidths(XOrg, YOrg, XMax, YMax);

      XStep = (XMax - XOrg) / Width;

      YStep = (YMax - YOrg) / Height;

      for (i = 0; i <= Width; i++)

        for (j = 0; j <= Height; j++)

        {

          XPos = XOrg + i * XStep;

          YPos = YOrg + j * YStep;

          XIter = 0.0;

          YIter = 0.0;

          Steps =0;

          Done = False;

          do {

            Calculate(XPos, YPos, XIter, YIter);

            Steps++;

            if (Distance(XIter, YIter) >= 2.0)

              Done = True;

            if (Steps == FDepth)

              Done = True;

          } while (!Done);

          if (Steps < FDepth)

            SetPixel(Canvas->Handle, i, j, GetColor(Steps));

          Application->ProcessMessages();

          if (FQuitDrawing)

            break;

      }

      return True;

    }

    void __fastcall TMandelForm::FormResize(TObject *Sender)

    {

      SetBoundary(0, 0, ClientWidth, ClientHeight);

    }

    void __fastcall TMandelForm::Timer1Timer(TObject *Sender)

    {

      Timer1->Enabled = False;

      Run();

    }

    void __fastcall TMandelForm::FormDblClick(TObject *Sender)

    {

      FQuitDrawing = True;

    }

    void __fastcall TMandelForm::FormMouseMove(TObject *Sender, TShiftState Shift,

                                               int X, int Y)

    {

      if (FDrawing)

      {

        Canvas->Pen->Mode = pmNotXor;

        if (FShapeRect.Right != -32000)

          DrawShape();

        FShapeRect.Right = X;

        FShapeRect.Bottom = Y;

        DrawShape();

      }

    }

    //--------------------------------------------------------------------------

When this program is launched, you see the opening form shown in Figure 7.5. The menu for this form will let you select secondary forms that draw fractal ferns (Figure 7.6), squares (Figure 7.7), and a rather hastily thrown together version of the Mandelbrot set (Figure 7.8).
FIGURE 7.5. The main form for the Fractals program features a bitmap made in Caligari's TrueSpace program.
FIGURE 7.6. A fractal fern drawn with VCL code.
FIGURE 7.7. These squares are animated and appear to be swirling away from the user.
FIGURE 7.8. You can draw a square on the form with the mouse to zoom in on any part of the Mandelbrot set.

This book is not about mathematics, so I will not give an in-depth explanation of any of these forms. Instead, I will simply give a quick overview of each of the major forms from the program. The key point to grasp is simply that you can use the VCL to create programs that create rather elaborate graphics.

The `Fern` form uses a global variable called `Count` to check the depth of detail to which the fern is drawn. When the detail reaches a certain point, the image is erased, and the drawing is begun anew.

The output for the Fern program is handled by a single routine found in the `DoPaint` method that draws a point to the screen:

    Canvas->Pixels[(x * MaxY / 11 + MaxX / 2)]

      [(y * - MaxY / 11 + MaxY)] =  clGreen;

A `Timer` component dropped on the form calls the `DoPaint` method at periodic intervals. By laboriously drawing pixels to the screen, one at a time, the `DoPaint` method slowly builds up a fractal image of a fern. Needless to say, it is the array-based math in the program that calculates where to draw the pixels so that the fern takes on a "life of its own." The `Squares` form creates the illusion that it is animating a set of squares by rotating them one inside the next, so that they appear to be receding into the distance as they spin away from the user. In fact, the code only calculates how to draw the squares once, and then animates the palette with which the squares are colored:

    for (i = 1; i <= BoxCount; i++)

    {

      DrawSquare(Scale, Theta);

      Theta = Theta + 10;

      Scale = Scale * 0.85;

      Canvas->Pen->Color = Colors[i];

    }

    for (i = BoxCount - 1; i >= 1; i--)

      Colors[i] = Colors[i - 1];

    Colors[0] = TColor(RGB(Colors[0] + random(64),

                  Colors[0] + random(64),

                  Colors[0] + random(64)));

The first loop shown here draws each square in a different color from an array of 25 different colors:

    #define BoxCount 25

    TColor Colors[BoxCount];

The second loop shown above shifts each color up the array by one notch. The final statement sets the first element in the array to a new color.

When you watch the program in action, each color appears to be racing away from the user down through the swirling maze of squares. Of course, the squares themselves aren't really moving; it's just that the colors assigned to them change with each iteration of the main loop in the program.

The `Mandy` form uses standard code to draw the Mandelbrot set. The user can then left-click at one point and drag a square across a portion of the set that he or she would look to see in more detail. When the user lets go of the left mouse button, the highlighted part of the image will be redrawn at a high magnification.

The whole point of the Mandelbrot set, of course, is that there is no end to the degree of detail with which you can view it. Of course, in this version, if you zoom in close enough, some of the detail gets lost. You could add code to the program that would fix this problem, but the current implementation should provide enough fun for most users.

I provide only minimal code that checks all the possible errors the user could make, such as clicking once on the form in the same spot:

    void TMandelForm::SetMouseUpPos(int ScrX1, int ScrY1)

    {

      FScrMaxX = ScrX1;

      FScrMaxY = ScrY1;

      if ((FScrMaxX - FScrOrgX) > 10)

        Run();

    }

Here you can see that I check to make sure there is at least 10 pixels of difference between the location where the user clicked the mouse and the location where he let go of the mouse. This helps to protect the user from feeding invalid data to the routines that calculate the area to draw. However, this code is not foolproof, and you might find yourself in a situation where the code is taking a ridiculously long time to finish its calculations. In that case, you can just double-click the form to stop the drawing.

You have already seen the rubber band technology used to draw a square on the form that delineates the next frame to be viewed. The code that performs that function for the `MandyForm` is taken verbatim from the DrawShapes program shown earlier in this chapter.

### Summary

In this chapter you have had an overview of graphics programming with the VCL. I have made no attempt to cover all facets of this subject, but hopefully you now have enough information to meet most of your needs.

Key subjects covered in this chapter include the `TCanvas` object, used for drawing shapes or words on a form or component surface. You also saw the `TFont`, `Tbrush`, and `TPen` objects, which define the characteristics of the shapes or letters drawn on a component. The `TMetaFile` and `TBitmap` objects were also introduced, though I never did do anything with the simple and easy-to-use `TIcon` object. The last section of the chapter had a bit of fun drawing flashy images to the screen.

One related subject that was not covered in this chapter is the `TImageList` object. If you are interested in this subject, you might want to look at the KdAddExplore program found on the CD-ROM that accompanies this book. The program should be in the `Chap14` directory. The `TImageList` component provides a means of storing a list of images in memory while taking up the minimum possible amount of system resources. It is much less expensive to store five images in an image list than it would be to use five separate bitmaps in your program.

©Copyright, Macmillan Computer Publishing. All rights reserved.

| Use of this site is subject certain Terms & Conditions. Copyright (c) 1996-1999 EarthWeb, Inc.. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Please read our privacy policy for details.