All graphic control components in the VCL provide the Canvas property a drawing surface for objects that draw images of themselves. In contrast to the window controls, Windows doesn't repaint such controls automatically, when, for example, the window pops up from the background. If you want to keep the content of the canvas highly dynamic, your program has to do the necessary bookkeeping of the drawing commands itself. In this article, we'll explain how to solve this problem by the metafile mechanism that easily records the necessary commandsjust like a VCR such that you only have to replay the tape, so to speak, to (re)paint the graphics.
Declare a variable of type TMetafile* in your form class. Create a
metafile object at the beginning of your application, that is, in the
constructor of the form:aMetafile = new TMetafile; To draw something that should be saved in the metafile, you must create a
metafile canvas. The simplest way to do this is with the command: | mfCanvas = new TMetafileCanvas(aMetafile, 0); Now you can draw objects on your mfCanvas just like on any other canvas.
The differences are that the picture is only created in memory and not on the
screen, and that all commands are recorded.
|
After your drawing is complete, you should always add the command delete
mfCanvas. The recording ends when the mfCanvas object is destroyed. This is
also the moment when the information is transmitted to the metafile.
|
Now you may replay the recorded operations easily. Every canvaslike the
one in a PaintBoxhas a Draw method that takes the metafile and the coordinates
of the upper-left corner and starts rendering. For example: | PaintBox->Canvas->Draw(0, 0, aMetafile); |
Figure A: The MetaDraw application comprises just a PaintBox and a ToolBar.
We create the metafile object in the constructor of the main form, like we did in step 1. In addition, we initialize a couple of internal variables:
__fastcall TDrawForm::TDrawForm(
TComponent* Owner) : TForm(Owner)
{
x_start = 0;
y_start = 0;
mouse_down = false;
cbColor->ItemIndex = 1;
aMetafile = new TMetafile;
aMetafile->Width = PaintBox->Width;
aMetafile->Height = PaintBox->Height;
Application->ShowHint = true;
}
The crucial events of the application are the pressing of the mouse button and
the mouse movement. When the users press the button, we remember the position
of the pointer and move the pen there. The following code illustrates this:
void __fastcall
TDrawForm::PaintBoxMouseDown(
TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
// Remember position.
x_start = X;
y_start = Y;
x_prev = x_start;
y_prev = y_start;
// Move pen to this position.
PaintBox->Canvas->MoveTo(X,Y);
// Remember pressing of mouse button.
mouse_down = true;
}
When the users move the mouse pointerwhen the OnMouseEvent occurswe delete the
current line and draw a new one, so they can always see what kind of line they
just drew. As this can only happen during direct user interaction, we can paint
directly on the form.
The interesting part comes when the users release the mouse button. Then we regard the present act of drawing as complete and save it to our metafile. The entire routine is given in Listing A; it follows the procedure proposed above.
Listing A: OnMouseUp event
void __fastcall
TDrawForm::PaintBoxMouseUp
(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
mouse_down = false;
TColor col;
switch (cbColor->ItemIndex)
{
case 0: col = clAqua;
break;
// ...
// More colors
}
// Create a canvas to draw into the
// metafile.
mfCanvas = new TMetafileCanvas(
aMetafile, 0);
// Draw existing lines on the canvas.
mfCanvas->Draw(0,0,aMetafile);
// Draw the line.
mfCanvas->Pen->Color = col;
mfCanvas->MoveTo(x_start,y_start);
mfCanvas->LineTo(X,Y);
// Free the canvas.
delete mfCanvas;
// Update the window.
PaintBox->Invalidate();
PaintBoxPaint(Sender);
}
First, we create the metafile canvas and draw all previously saved lines
on it. Then, we set the desired color of the line and add the line to the
drawing. Finally, we free the canvas to transmit the additions into the
metafile and update the screen by setting it to invalid.
This command forces a repainting of the box. To be precise, it causes an OnPaint event in our C++Builder program. In the handler of this event, all we have to do is replay the contents of the metafile:
void __fastcall TDrawForm::PaintBoxPaint(TObject *Sender)
{
PaintBox->Canvas->Draw(0,0,aMetafile);
}