📄 ch07.htm
字号:
<UL>
<LI>If you <TT>XOR</TT> a square to the screen, the square will show up clearly.
<P>
<LI>If you <TT>XOR</TT> that same image again in the same location, the image will
disappear.
</UL>
<P>Such are the virtues of simple logical operations in graphics mode.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>Aficionados of graphics logic will
note that the logical operation employed by
<TT>DrawShapes</TT> is a variation on
the exclusive <TT>OR</TT> (<TT>XOR</TT>) 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:</P>
<PRE><FONT COLOR="#0066FF">R2_XOR: final pixel = pen ^ screen pixel
R2_NOTXORPEN : final pixel = ~(pen ^ screen pixel)</FONT></PRE>
<P>This code tests to see whether the pixels to be <TT>XOR</TT>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. <BR>
<BR>
If you were working directly in the Windows
API, you would work with a constant called
<TT>RT_NOTXORPEN</TT> rather than <TT>pmNotXor</TT>. 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. <BR>
<BR>
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.
<HR>
</BLOCKQUOTE>
<P>Notice that <TT>FormMouseMove</TT> calls <TT>DrawShape</TT> twice. The first time,
it passes in the dimensions of the old figure that needs to be erased. That means
it <TT>XOR</TT>s the
same image directly on top of the original image, thereby erasing
it. Then <TT>FormMouseMove</TT> records the location of the latest <TT>WM_MOUSEMOVE</TT>
message and passes this new information to <TT>DrawShape</TT>, 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.</P>
<P>In the <TT>DrawImage</TT> function, <TT>Metaphor</TT> 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:</P>
<PRE><FONT COLOR="#0066FF">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:
;
}
}
</FONT></PRE>
<P>This code sets the current <TT>Pen</TT> and <TT>Brush</TT> to the values
chosen
by the user. It then uses a <TT>switch</TT> statement to select the proper type of
shape to draw to the screen. Most of these private variables such as <TT>FPenColor</TT>
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.</P>
<P>Notice that when drawing these shapes, there is no need to track the HDC of the
current <TT>Canvas</TT>. One of the primary goals of the <TT>TCanvas</TT> 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."</P>
<P>The final step in the whole operation occurs when the user lifts his finger off
the mouse:</P>
<PRE><FONT
COLOR="#0066FF">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();
}
</FONT></PRE>
<P>This code performs the following actions:
<UL>
<LI>A flag is set stating that the user has decided to stop drawing.
<P>
<LI>The final dimensions of the shape are recorded.
<P>
<LI>The mode for the <TT>Pen</TT> is switched from <TT>pmNotXor</TT> to the default
value, which is <TT>pmCopy</TT>.
<P>
<LI>The final image is painted to the screen.
</UL>
<P>The code that paints the final shape takes into account the colors and
the pen
thickness that the user selected with the menus.</P>
<P>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:
<UL>
<LI>Remember where the <TT>WM_LBUTTONDOWN</TT> message took place.
<P>
<LI>Draw the shape each time you get a <TT>WM_MOUSEMOVE</TT> message.
<P>
<LI>Draw the final shape when you
get a <TT>WM_LBUTTONUP</TT> message.
</UL>
<P>That's all there is to it.
<H4><A NAME="Heading13"></A><FONT COLOR="#000077">To GDI or not to GDI</FONT></H4>
<P>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.</P>
<P>If you want to access the HDC for a window, you can get at it through the <TT>Handle</TT>
property of the canvas:</P>
<PRE><FONT COLOR="#0066FF">MyOldPenHandle = SelectObject(Canvas->Handle, MyPen->Handle);
</FONT></PRE>
<P>In this case you are copying the <TT>Handle</TT> of a <TT>Pen</TT> object into
the HDC of the <TT>TCanvas</TT> object.</P>
<P>Having free
access to the <TT>Handle</TT> of the <TT>TCanvas</TT> 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.</P>
<P>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 <TT>Handle</TT> 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 <TT>TCanvas</TT> object provided by
the VCL.</P>
<P>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
<TT>TCanvas</TT> object altogether. The
reason I take this course is simply that the <TT>TCanvas</TT> object will sometimes
maintain the <TT>Canvas</TT>'s HDC on its own, and I therefore can't have complete
control over what is happening to it.</P>
<P>Here is a simple example of how to use the GDI directly inside a VCL program:</P>
<PRE><FONT COLOR="#0066FF">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);
</FONT></PRE>
<P>If you look at the code shown here, you will see that I get my own DC by calling
the Windows API function <TT>GetDC</TT>. I then select a new font into the DC. Notice
that
I use the VCL <TT>TFont</TT> object. I usually find it easier to manage <TT>Font</TT>s,
<TT>Pen</TT>s, and <TT>Brush</TT>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.</P>
<P>Here is an example of creating a VCL <TT>Font</TT> object from scratch:</P>
<PRE><FONT COLOR="#0066FF">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;
</FONT></PRE>
<P>This code allocates memory for a <TT>Font</TT> object, assigns some values to
its key properties, and then copies the <TT>Handle</TT> of the <TT>Font</TT> object
into the current DC. Notice that I still have to save the old
<TT>Font</TT> 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 <TT>Font</TT> object I created.</P>
<P>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.
<H3><A NAME="Heading14"></A><FONT COLOR="#000077">Using TImage, Saving and Loading
Bitmaps</FONT></H3>
<P>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.</P>
<P>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 <TT>TImage</TT>.
This control provides you with a <TT>Canvas</TT> on which you can draw, and gives
you the
ability to convert this <TT>Canvas</TT> into a bitmap.</P>
<P>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.<BR>
<BR>
<A NAME="Heading15"></A><FONT COLOR="#000077"><B>Listing 7.3. The main module for
the BitmapShapes program.</B></FONT></P>
<PRE><FONT
COLOR="#0066FF">///////////////////////////////////////
// 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)
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -