Graphics and multimedia elements can add polish to your applications. Delphi offers a variety of ways to introduce these features into your application. To add graphical elements, you can insert pre-drawn pictures at design time, create them using graphical controls at design time, or draw them dynamically at runtime. To add multimedia capabilities, Delphi includes special components that can play audio and video clips.
The VCL graphics components encapsulate the Windows Graphics Device Interface (GDI), making it very easy to add graphics to your Windows programming.
To draw graphics in a Delphi application, you draw on an object's canvas, rather than directly on the object. The canvas is a property of the object, and is itself an object. A main advantage of the canvas object is that it handles resources effectively and it takes care of device context, so your programs can use the same methods regardless of whether you are drawing on the screen, to a printer, or on bitmaps or metafiles. Canvases are available only at runtime, so you do all your work with canvases by writing code.
Note: Since TCanvas is a wrapper resource manager around the Windows device context, you can also use all Windows GDI functions on the canvas. The Handle property of the canvas is the device context Handle.
How graphic images appear in your application depends on the type of object whose canvas you draw on. If you are drawing directly onto the canvas of a control, the picture is displayed immediately. However, if you draw on an offscreen image such as a TBitmap canvas, the image is not displayed until a control copies from the bitmap onto the control's canvas. That is, when drawing bitmaps and assigning them to an image control, the image appears only when the control has an opportunity to process its OnPaint message.
When working with graphics, you often encounter the terms drawing and painting:
The examples in the beginning of this chapter demonstrate how to draw various graphics, but they do so in response to OnPaint events. Later sections show how to do the same kind of drawing in response to other events.
At certain times, Windows determines that objects onscreen need to refresh their appearance, so it generates WM_PAINT messages, which the VCL routes to OnPaint events. The VCL calls any OnPaint event handler that you have written for that object when you use the Refresh method. The default name generated for the OnPaint event handler in a form is FormPaint. You may want to use the Refresh method at times to refresh a component or form. For example, you might call Refresh in the form's OnResize event handler to redisplay any graphics or if you want to paint a background on a form.
While some operating systems automatically handle the redrawing of the client area of a window that has been invalidated, Windows does not. In the Windows operating system anything drawn on the screen is permanent. When a form or control is temporarily obscured, for example during window dragging, the form or control must repaint the obscured area when it is re-exposed. For more information about the WM_PAINT message, see the Windows online Help.
If you use the TImage control, the painting and refreshing of the graphic contained in the TImage is handled automatically by the VCL. Drawing on a TImage creates a persistent image. Consequently, you do not need to do anything to redraw the contained image. In contrast, TPaintBox's canvas maps directly onto the screen device, so that anything drawn to the PaintBox's canvas is transitory. This is true of nearly all controls, including the form itself. Therefore, if you draw or paint on a TPaintBox in its constructor, you will need to add that code to your OnPaint event handler in order for image to be repainted each time the client area is invalidated.
The VCL provides the graphic objects shown in Table 7.1. These objects have methods to draw on the canvas, which are described in "Using Canvas methods to draw graphic objects" and to load and save to graphics files, as described in "Loading and saving graphics files".
Table 7.2 lists the commonly used properties of the Canvas object. For a complete list of properties and methods, see the TCanvas component in online Help.
These properties are described in more detail in "Using the properties of the Canvas object".
Table 7.3 is a list of several methods you can use:
These methods are described in more detail in "Using Canvas methods to draw graphic objects".
With the Canvas object, you can set the properties of a pen for drawing lines, a brush for filling shapes, a font for writing text, and an array of pixels to represent the image.
The Pen property of a canvas controls the way lines appear, including lines drawn as the outlines of shapes. Drawing a straight line is really just changing a group of pixels that lie between two points.
The pen itself has four properties you can change: Color, Width, Style, and Mode.
The values of these properties determine how the pen changes the pixels in the line. By default, every pen starts out black, with a width of 1 pixel, a solid style, and a mode called copy that overwrites anything already on the canvas.
You can set the color of a pen as you would any other Color property at runtime. A pen's color determines the color of the lines the pen draws, including lines drawn as the boundaries of shapes, as well as other lines and polylines. To change the pen color, assign a value to the Color property of the pen.
To let the user choose a new color for the pen, put a color grid on the pen's toolbar. A color grid can set both foreground and background colors. For a non-grid pen style, you must consider the background color, which is drawn in the gaps between line segments. Background color comes from the Brush color property.
Since the user chooses a new color by clicking the grid, this code changes the pen's color in response to the OnClick event:
procedure TForm1.PenColorClick(Sender: TObject); begin Canvas.Pen.Color := PenColor.ForegroundColor; end;
A pen's width determines the thickness, in pixels, of the lines it draws.
Note: When the thickness is greater than 1, Windows 95 always draw solid lines, no matter what the value of the pen's Style property.
To change the pen width, assign a numeric value to the pen's Width property.
Suppose you have a scroll bar on the pen's toolbar to set width values for the pen. And suppose you want to update the label next to the scroll bar to provide feedback to the user. Using the scroll bar's position to determine the pen width, you update the pen width every time the position changes.
This is how to handle the scroll bar's OnChange event:
procedure TForm1.PenWidthChange(Sender: TObject);
begin
Canvas.Pen.Width := PenWidth.Position; { set the pen width directly }
PenSize.Caption := IntToStr(PenWidth.Position); { convert to string for caption }
end;
A pen's Style property allows you to set solid lines, dashed lines, dotted lines, and so on.
Note: Windows 95 does not support dashed or dotted line styles for pens wider than one pixel and makes all larger pens solid, no matter what style you specify.
The task of setting the properties of pen is an ideal case for having different controls share same event handler to handle events. To determine which control actually got the event, you check the Sender parameter.
To create one click-event handler for six pen-style buttons on a pen's toolbar, do the following:
SetPenStyle.
Delphi generates an empty click-event handler called SetPenStyle and attaches it to the OnClick events of all six buttons.
procedure TForm1.SetPenStyle(Sender: TObject);
begin
with Canvas.Pen do
begin
if Sender = SolidPen then Style := psSolid
else if Sender = DashPen then Style := psDash
else if Sender = DotPen then Style := psDot
else if Sender = DashDotPen then Style := psDashDot
else if Sender = DashDotDotPen then Style := psDashDotDot
else if Sender = ClearPen then Style := psClear;
end;
end;
A pen's Mode property lets you specify various ways to combine the pen's color with the color on the canvas. For example, the pen could always be black, be an inverse of the canvas background color, inverse of the pen color, and so on. See TPen in online Help for details.
The current drawing position--the position from which the pen begins drawing its next line--is called the pen position. The canvas stores its pen position in its PenPos property. Pen position affects the drawing of lines only; for shapes and text, you specify all the coordinates you need.
To set the pen position, call the MoveTo method of the canvas. For example, the following code moves the pen position to the upper left corner of the canvas:
Canvas.MoveTo(0, 0);
Note: Drawing a line with the LineTo method also moves the current position to the endpoint of the line.
The Brush property of a canvas controls the way you fill areas, including the interior of shapes. Filling an area with a brush is a way of changing a large number of adjacent pixels in a specified way.
The brush has three properties you can manipulate:
The values of these properties determine the way the canvas fills shapes or other areas. By default, every brush starts out white, with a solid style and no pattern bitmap.
A brush's color determines what color the canvas uses to fill shapes. To change the fill color, assign a value to the brush's Color property. Brush is used for background color in text and line drawing so you typically set the background color property.
You can set the brush color just as you do the pen color, in response to a click on a color grid on the brush's toolbar (see "Changing the pen color"):
procedure TForm1.BrushColorClick(Sender: TObject); begin Canvas.Brush.Color := BrushColor.ForegroundColor; end;
A brush style determines what pattern the canvas uses to fill shapes. It lets you specify various ways to combine the brush's color with any colors already on the canvas. The predefined styles include solid color, no color, and various line and hatch patterns.
To change the style of a brush, set its Style property to one of the predefined values: bsSolid, bsClear, bsHorizontal, bsVertical, bsFDiagonal, bsBDiagonal, bsCross, or bsDiagCross.
This example sets brush styles by sharing a click-event handler for a set of eight brush-style buttons. All eight buttons are selected, the Object Inspector|Events|OnClick is set, and the OnClick handler is named SetBrushStyle. Here is the handler code:
procedure TForm1.SetBrushStyle(Sender: TObject);
begin
with Canvas.Brush do
begin
if Sender = SolidBrush then Style := bsSolid
else if Sender = ClearBrush then Style := bsClear
else if Sender = HorizontalBrush then Style := bsHorizontal
else if Sender = VerticalBrush then Style := bsVertical
else if Sender = FDiagonalBrush then Style := bsFDiagonal
else if Sender = BDiagonalBrush then Style := bsBDiagonal
else if Sender = CrossBrush then Style := bsCross
else if Sender = DiagCrossBrush then Style := bsDiagCross;
end;
end;
A brush's Bitmap property lets you specify a bitmap image for the brush to use as a pattern for filling shapes and other areas.
The following example loads a bitmap from a file and assigns it to the Brush of the Canvas of Form1:
var
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('MyBitmap.bmp');
Form1.Canvas.Brush.Bitmap := Bitmap;
Form1.Canvas.FillRect(Rect(0,0,100,100));
finally
Form1.Canvas.Brush.Bitmap := nil;
Bitmap.Free;
end;
end;
Note: The brush does not assume ownership of a bitmap object assigned to its Bitmap property. You must ensure that the Bitmap object remain valid for the lifetime of the Brush, and you must free the Bitmap object yourself afterwards.
You will notice that every canvas has an indexed Pixels property that represents the individual colored points that make up the image on the canvas. You rarely need to access Pixels directly, it is available only for convenience to perform small actions such as finding or setting a pixel's color.
Note: Setting and getting individual pixels is thousands of times slower than performing graphics operations on regions. Do not use the Pixel array property to access the image pixels of a general array. For high-performance access to image pixels, see the TBitmap.ScanLine property.
This section shows how to use some common methods to draw graphic objects. It covers:
A canvas can draw straight lines and polylines. A straight line is just a line of pixels connecting two points. A polyline is a series of straight lines, connected end-to-end. The canvas draws all lines using its pen.
To draw a straight line on a canvas, use the LineTo method of the canvas.
LineTo draws a line from the current pen position to the point you specify and makes the endpoint of the line the current position. The canvas draws the line using its pen.
For example, the following method draws crossed diagonal lines across a form whenever the form is painted:
procedure TForm1.FormPaint(Sender: TObject); begin with Canvas do begin MoveTo(0, 0); LineTo(ClientWidth, ClientHeight); MoveTo(0, ClientHeight); LineTo(ClientWidth, 0); end; end;
In addition to individual lines, the canvas can also draw polylines, which are groups of any number of connected line segments.
To draw a polyline on a canvas, call the Polyline method of the canvas.
The parameter passed to the PolyLine method is an array of points. You can think of a polyline as performing a MoveTo on the first point and LineTo on each successive point. For drawing multiple lines, Polyline is faster than using the MoveTo method and the LineTo method because it eliminates a lot of call overhead.
The following method, for example, draws a rhombus in a form:
procedure TForm1.FormPaint(Sender: TObject); begin with Canvas do PolyLine([Point(0, 0), Point(50, 0), Point(75, 50), Point(25, 50), Point(0, 0)]); end;
This example takes advantage of Delphi's ability to create an open-array parameter on-the-fly. You can pass any array of points, but an easy way to construct an array quickly is to put its elements in brackets and pass the whole thing as a parameter. For more information, see online Help.
Canvases have methods for drawing different kinds of shapes. The canvas draws the outline of a shape with its pen, then fills the interior with its brush. The line that forms the border for the shape is controlled by the current Pen object.
To draw a rectangle or ellipse on a canvas, call the canvas's Rectangle method or Ellipse method, passing the coordinates of a bounding rectangle.
The Rectangle method draws the bounding rectangle; Ellipse draws an ellipse that touches all sides of the rectangle.
The following method draws a rectangle filling a form's upper left quadrant, then draws an ellipse in the same area:
procedure TForm1.FormPaint(Sender: TObject); begin Canvas.Rectangle(0, 0, ClientWidth div 2, ClientHeight div 2); Canvas.Ellipse(0, 0, ClientWidth div 2, ClientHeight div 2); end;
To draw a rounded rectangle on a canvas, call the canvas's RoundRect method.
The first four parameters passed to RoundRect are a bounding rectangle, just as for the Rectangle method or the Ellipse method. RoundRect takes two more parameters that indicate how to draw the rounded corners.
The following method, for example, draws a rounded rectangle in a form's upper left quadrant, rounding the corners as sections of a circle with a diameter of 10 pixels:
procedure TForm1.FormPaint(Sender: TObject); begin Canvas.RoundRect(0, 0, ClientWidth div 2, ClientHeight div 2, 10, 10); end;
To draw a polygon with any number of sides on a canvas, call the Polygon method of the canvas.
Polygon takes an array of points as its only parameter and connects the points with the pen, then connects the last point to the first to close the polygon. After drawing the lines, Polygon uses the brush to fill the area inside the polygon.
For example, the following code draws a right triangle in the lower left half of a form:
procedure TForm1.FormPaint(Sender: TObject); begin Canvas.Polygon([Point(0, 0), Point(0, ClientHeight), Point(ClientWidth, ClientHeight)]); end;
Various drawing methods (rectangle, shape, line, and so on) are typically available on the toolbar and button panel. Applications can respond to clicks on speed buttons to set the desired drawing objects. This section describes how to:
A graphics program needs to keep track of what kind of drawing tool (such as a line, rectangle, ellipse, or rounded rectangle) a user might want to use at any given time. You could assign numbers to each kind of tool, but then you would have to remember what each number stands for. You can do that more easily by assigning mnemonic constant names to each number, but your code won't be able to distinguish which numbers are in the proper range and of the right type. Fortunately, Object Pascal provides a means to handle both of these shortcomings. You can declare an enumerated type.
An enumerated type is really just a shorthand way of assigning sequential values to constants. Since it's also a type declaration, you can use Object Pascal's type-checking to ensure that you assign only those specific values.
To declare an enumerated type, use the reserved work type, followed by an identifier for the type, then an equal sign, and the identifiers for the values in the type in parentheses, separated by commas.
For example, the following code declares an enumerated type for each drawing tool available in a graphics application:
type TDrawingTool = (dtLine, dtRectangle, dtEllipse, dtRoundRect);
By convention, type identifiers begin with the letter T, and groups of similar constants (such as those making up an enumerated type) begin with a 2-letter prefix (such as dt for "drawing tool").
The declaration of the TDrawingTool type is equivalent to declaring a group of constants:
const dtLine = 0; dtRectangle = 1; dtEllipse = 2; dtRoundRect = 3;
The main difference is that by declaring the enumerated type, you give the constants not just a value, but also a type, which enables you to use Object Pascal's type-checking to prevent many errors. A variable of type TDrawingTool can be assigned only one of the constants dtLine..dtRoundRect. Attempting to assign some other number (even one in the range 0..3) generates a compile-time error.
In the following code, a field added to a form keeps track of the form's drawing tool:
type
TDrawingTool = (dtLine, dtRectangle, dtEllipse, dtRoundRect);
TForm1 = class(TForm)
... { method declarations }
public
Drawing: Boolean;
Origin, MovePt: TPoint;
DrawingTool: TDrawingTool; { field to hold current tool }
end;
Each drawing tool needs an associated OnClick event handler. Suppose your application had a toolbar button for each of four drawing tools: line, rectangle, ellipse, and rounded rectangle. You would attach the following event handlers to the OnClick events of the four drawing-tool buttons, setting DrawingTool to the appropriate value for each:
procedure TForm1.LineButtonClick(Sender: TObject); { LineButton }
begin
DrawingTool := dtLine;
end;
procedure TForm1.RectangleButtonClick(Sender: TObject); { RectangleButton }
begin
DrawingTool := dtRectangle;
end;
procedure TForm1.EllipseButtonClick(Sender: TObject); { EllipseButton }
begin
DrawingTool := dtEllipse;
end;
procedure TForm1.RoundedRectButtonClick(Sender: TObject); { RoundRectButton }
begin
DrawingTool := dtRoundRect;
end;
Now that you can tell what tool to use, you must indicate how to draw the different shapes. The only methods that perform any drawing are the mouse-move and mouse-up handlers, and the only drawing code draws lines, no matter what tool is selected.
To use different drawing tools, your code needs to specify how to draw, based on the selected tool. You add this instruction to each tool's event handler.
Drawing shapes is just as easy as drawing lines: Each one takes a single statement; you just need the coordinates.
Here's a rewrite of the OnMouseUp event handler that draws shapes for all four tools:
procedure TForm1.FormMouseUp(Sender: TObject);
begin
case DrawingTool of
dtLine:
begin
Canvas.MoveTo(Origin.X, Origin.Y);
Canvas.LineTo(X, Y)
end;
dtRectangle: Canvas.Rectangle(Origin.X, Origin.Y, X, Y);
dtEllipse: Canvas.Ellipse(Origin.X, Origin.Y, X, Y);
dtRoundRect: Canvas.RoundRect(Origin.X, Origin.Y, X, Y,
(Origin.X - X) div 2, (Origin.Y - Y) div 2);
end;
Drawing := False;
end;
Of course, you also need to update the OnMouseMove handler to draw shapes:
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if Drawing then
begin
Canvas.Pen.Mode := pmNotXor;
case DrawingTool of
dtLine: begin
Canvas.MoveTo(Origin.X, Origin.Y);
Canvas.LineTo(MovePt.X, MovePt.Y);
Canvas.MoveTo(Origin.X, Origin.Y);
Canvas.LineTo(X, Y);
end;
dtRectangle: begin
Canvas.Rectangle(Origin.X, Origin.Y, MovePt.X, MovePt.Y);
Canvas.Rectangle(Origin.X, Origin.Y, X, Y);
end;
dtEllipse: begin
Canvas.Ellipse(Origin.X, Origin.Y, X, Y);
Canvas.Ellipse(Origin.X, Origin.Y, X, Y);
end;
dtRoundRect: begin
Canvas.RoundRect(Origin.X, Origin.Y, X, Y,
(Origin.X - X) div 2, (Origin.Y - Y) div 2);
Canvas.RoundRect(Origin.X, Origin.Y, X, Y,
(Origin.X - X) div 2, (Origin.Y - Y) div 2);
end;
end;
MovePt := Point(X, Y);
end;
Canvas.Pen.Mode := pmCopy;
end;
Typically, all the repetitious code that is in the above example would be in a separate routine. The next section shows all the shape-drawing code in a single routine that all mouse-event handlers can call.
Any time you find that many your event handlers use the same code, you can make your application more efficient by moving the repeated code into a routine that all event handlers can share.
You can add the declaration in either the public or private parts at the end of the form object's declaration. If the code is just sharing the details of handling some events, it's probably safest to make the shared method private.
The header for the method implementation must match the declaration exactly, with the same parameters in the same order.
The following code adds a method to the form called DrawShape and calls it from each of the handlers. First, the declaration of DrawShape is added to the form object's declaration:
type
TForm1 = class(TForm)
... { fields and methods declared here}
public
{ Public declarations }
procedure DrawShape(TopLeft, BottomRight: TPoint; AMode: TPenMode);
end;
Then, the implementation of DrawShape is written in the implementation part of the unit:
implementation
{$R *.FRM}
... { other method implementations omitted for brevity }
procedure TForm1.DrawShape(TopLeft, BottomRight: TPoint; AMode: TPenMode);
begin
with Canvas do
begin
Pen.Mode := AMode;
case DrawingTool of
dtLine:
begin
MoveTo(TopLeft.X, TopLeft.Y);
LineTo(BottomRight.X, BottomRight.Y);
end;
dtRectangle: Rectangle(TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y);
dtEllipse: Ellipse(TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y);
dtRoundRect: RoundRect(TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y,
(TopLeft.X - BottomRight.X) div 2, (TopLeft.Y - BottomRight.Y) div 2);
end;
end;
end;
The other event handlers are modified to call DrawShape.
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
DrawShape(Origin, Point(X, Y), pmCopy); { draw the final shape }
Drawing := False;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Drawing then
begin
DrawShape(Origin, MovePt, pmNotXor); { erase the previous shape }
MovePt := Point(X, Y); { record the current point }
DrawShape(Origin, MovePt, pmNotXor); { draw the current shape }
end;
end;
You don't need any components to manipulate your application's graphic objects. You can construct, draw on, save, and destroy graphic objects without ever drawing anything on screen. In fact, your applications rarely draw directly on a form. More often, an application operates on graphics and then uses a VCL image control component to display the graphic on a form.
Once you move the application's drawing to the graphic in the image control, it is easy to add printing, Clipboard, and loading and saving operations for any graphic objects. graphic objects can be bitmap files, metafiles, icons or whatever other graphics classes that have been installed such as JPEG graphics.
Note: Because you are drawing on an offscreen image such as a TBitmap canvas, the image is not displayed until a control copies from a bitmap onto the control's canvas. That is, when drawing bitmaps and assigning them to an image control, the image appears only when the control has an opportunity to process its paint message. Contrastly, if you are drawing directly onto the canvas property of a control, the picture object is displayed immediately.
The graphic need not be the same size as the form: it can be either smaller or larger. By adding a scroll box control to the form and placing the graphic image inside it, you can display graphics that are much larger than the form or even larger than the screen. To add a scrollable graphic first you add a TScrollbox component and then you add the image control.
An image control is a container component that allows you to display your bitmap objects. You use an image control to hold a bitmap that is not necessarily displayed all the time, or which an application needs to use to generate other pictures.
Note: "Adding graphics to controls" shows how to use graphics in controls.
You can place an image control anywhere on a form. If you take advantage of the image control's ability to size itself to its picture, you need to set the top left corner only. If the image control is a nonvisible holder for a bitmap, you can place it anywhere, just as you would a nonvisual component.
If you drop the image control on a scroll box already aligned to the form's client area, this assures that the scroll box adds any scroll bars necessary to access offscreen portions of the image's picture. Then set the image control's properties.
When you place an image control, it is simply a container. However, you can set the image control's Picture property at design time to contain a static graphic. The control can also load its picture from a file at runtime, as described in "Loading and saving graphics files".
To create a blank bitmap when the application starts,
In this example, the image is in the application's main form, Form1, so the code attaches a handler to Form1's OnCreate event:
procedure TForm1.FormCreate(Sender: TObject);
var
Bitmap: TBitmap; { temporary variable to hold the bitmap }
begin
Bitmap := TBitmap.Create; { construct the bitmap object }
Bitmap.Width := 200; { assign the initial width... }
Bitmap.Height := 200; { ...and the initial height }
Image.Picture.Graphic := Bitmap; { assign the bitmap to the image control }
end;
Assigning the bitmap to the picture's Graphic property gives ownership of the bitmap to the picture object. The picture object destroys the bitmap when it finishes with it, so you should not destroy the bitmap object. You can assign a different bitmap to the picture (see "Replacing the picture"), at which point the picture disposes of the old bitmap and assumes ownership of the new one.
If you run the application now, you see that client area of the form has a white region, representing the bitmap. If you size the window so that the client area cannot display the entire image, you'll see that the scroll box automatically shows scroll bars to allow display of the rest of the image. But if you try to draw on the image, you don't get any graphics, because the application is still drawing on the form, which is now behind the image and the scroll box.
To draw on a bitmap, use the image control's canvas and attach the mouse-event handlers to the appropriate events in the image control. Typically you would use region operations (fills, rectangles, polylines, and so on). These are fast and efficient methods of drawing.
An efficient way to draw images when you need to access individual pixels is to use the bitmap ScanLine property. For general-purpose usage, you can set up the bitmap pixel format to 24 bits and then treat the pointer returned from ScanLine as an array of RGB. Otherwise, you will need to know the native format of the ScanLine property. This example shows how to use ScanLine to get pixels one line at a time.
procedure TForm1.Button1Click(Sender: TObject);
// This example shows drawing directly to the BitMap
var
x,y : integer;
BitMap : TBitMap;
P : PByteArray;
begin
BitMap := TBitMap.create;
try
BitMap.LoadFromFile('C:\Program Files\Borland\Delphi 4\Images\Splash\256color\
factory.bmp');
for y := 0 to BitMap.height -1 do
begin
P := BitMap.ScanLine[y];
for x := 0 to BitMap.width -1 do
P[x] := y;
end;
canvas.draw(0,0,BitMap);
finally
BitMap.free;
end;
end;
Graphic images that exist only for the duration of one running of an application are of very limited value. Often, you either want to use the same picture every time, or you want to save a created picture for later use. The VCL's image control makes it easy to load pictures from a file and save them again.
The VCL components you use to load, save, and replace graphic images support many graphic formats including bitmap files, metafiles, glyphs, and so on. They also support installable graphic classes.
The way to load and save graphics files is the similar to any other files and is described in the following sections:
Your application should provide the ability to load a picture from a file if your application needs to modify the picture or if you want to store the picture outside the application so a person or another application can modify the picture.
To load a graphics file into an image control, call the LoadFromFile method of the image control's Picture object.
The following code gets a file name from an open-file dialog box, and then loads that file into an image control named Image:
procedure TForm1.Open1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
CurrentFile := OpenDialog1.FileName;
Image.Picture.LoadFromFile(CurrentFile);
end;
end;
The VCL picture object can load and save graphics in several formats, and you can create and register your own graphic-file formats so that picture objects can load and store them as well.
To save the contents of an image control in a file, call the SaveToFile method of the image control's Picture object.
The SaveToFile method requires the name of a file in which to save. If the picture is newly created, it might not have a file name, or a user might want to save an existing picture in a different file. In either case, the application needs to get a file name from the user before saving, as shown in the next section.
The following pair of event handlers, attached to the File|Save and File|Save As menu items, respectively, handle the resaving of named files, saving of unnamed files, and saving existing files under new names.
procedure TForm1.Save1Click(Sender: TObject);
begin
if CurrentFile <> '' then
Image.Picture.SaveToFile(CurrentFile) { save if already named }
else SaveAs1Click(Sender); { otherwise get a name }
end;
procedure TForm1.Saveas1Click(Sender: TObject);
begin
if SaveDialog1.Execute then { get a file name }
begin
CurrentFile := SaveDialog1.FileName; { save the user-specified name }
Save1Click(Sender); { then save normally }
end;
end;
You can replace the picture in an image control at any time. If you assign a new graphic to a picture that already has a graphic, the new graphic replaces the existing one.
To replace the picture in an image control, assign a new graphic to the image control's Picture object.
Creating the new graphic is the same process you used to create the initial graphic (see "Setting the initial bitmap size"), but you should also provide a way for the user to choose a size other than the default size used for the initial graphic. An easy way to provide that option is to present a dialog box, such as the one in Figure 7.1.
This particular dialog box is created in the BMPDlg unit included with the GraphEx project (in the EXAMPLES\DOC\GRAPHEX directory).
With such a dialog box in your project, add it to the uses clause in the unit for your main form. You can then attach an event handler to the File|New menu item's OnClick event. Here's an example:
procedure TForm1.New1Click(Sender: TObject);
var
Bitmap: TBitmap; { temporary variable for the new bitmap }
begin
with NewBMPForm do
begin
ActiveControl := WidthEdit; { make sure focus is on width field }
WidthEdit.Text := IntToStr(Image.Picture.Graphic.Width); { use current dimensions... }
HeightEdit.Text := IntToStr(Image.Picture.Graphic.Height); { ...as default }
if ShowModal <> idCancel then { continue if user doesn't cancel dialog box }
begin
Bitmap := TBitmap.Create; { create fresh bitmap object }
Bitmap.Width := StrToInt(WidthEdit.Text); { use specified width }
Bitmap.Height := StrToInt(HeightEdit.Text); { use specified height }
Image.Picture.Graphic := Bitmap; { replace graphic with new bitmap }
CurrentFile := ''; { indicate unnamed file }
end;
end;
end;
Note: Assigning a new bitmap to the picture object's Graphic property causes the picture object to destroy the existing bitmap and take ownership of the new one. The VCL handles the details of freeing the resources associated with the previous bitmap automatically.
You can use the Windows Clipboard to copy and paste graphics within your applications or to exchange graphics with other applications. The VCL's Clipboard object makes it easy to handle different kinds of information, including graphics.
Before you can use the Clipboard object in your application, you must add the Clipbrd unit to the uses clause of any unit that needs to access Clipboard data.
You can copy any picture, including the contents of image controls, to the Clipboard. Once on the Clipboard, the picture is available to all Windows applications.
To copy a picture to the Clipboard, assign the picture to the Clipboard object using the Assign method.
This code shows how to copy the picture from an image control named Image to the Clipboard in response to a click on an Edit|Copy menu item:
procedure TForm1.Copy1Click(Sender: TObject); begin Clipboard.Assign(Image.Picture) end.
Cutting a graphic to the Clipboard is exactly like copying it, but you also erase the graphic from the source.
To cut a graphic from a picture to the Clipboard, first copy it to the Clipboard, then erase the original.
In most cases, the only issue with cutting is how to show that the original image is erased. Setting the area to white is a common solution, as shown in the following code that attaches an event handler to the OnClick event of the Edit|Cut menu item:
procedure TForm1.Cut1Click(Sender: TObject);
var
ARect: TRect;
begin
Copy1Click(Sender); { copy picture to Clipboard }
with Image.Canvas do
begin
CopyMode := cmWhiteness; { copy everything as white }
ARect := Rect(0, 0, Image.Width, Image.Height); { get bitmap rectangle }
CopyRect(ARect, Image.Canvas, ARect); { copy bitmap over itself }
CopyMode := cmSrcCopy; { restore normal mode }
end;
end;
If the Windows Clipboard contains a bitmapped graphic, you can paste it into any image object, including image controls and the surface of a form.
To paste a graphic from the Clipboard,
HasFormat is a Boolean function. It returns True if the Clipboard contains an item of the type specified in the parameter. To test for graphics, you pass CF_BITMAP.
This code shows how to paste a picture from the Clipboard into an image control in response to a click on an Edit|Paste menu item:
procedure TForm1.PasteButtonClick(Sender: TObject);
var
Bitmap: TBitmap;
begin
if Clipboard.HasFormat(CF_BITMAP) then { is there a bitmap on the Clipboard? )
begin
Image.Picture.Bitmap.Assign(Clipboard);
end;
end;
The graphic on the Clipboard could come from this application, or it could have been copied from another application, such as Windows Paintbrush. You do not need to check the clipboard format in this case because the paste menu should be disabled when the clipboard does not contain a supported format.
This section walks you through the details of implementing the "rubber banding" effect in an graphics application that tracks mouse movements as the user draws a graphic at runtime. The example code in this section is taken from a sample application located in the EXAMPLES\DOC\GRAPHEX directory. The application draws lines and shapes on a window's canvas in response to clicks and drags: pressing a mouse button starts drawing, and releasing the button ends the drawing.
To start with, the example code shows how to draw on the surface of the main form. Later examples demonstrate drawing on a bitmap.
Your application can respond to the mouse actions: mouse-button down, mouse moved, and mouse-button up. It can also respond to a click (a complete press-and-release, all in one place) that can be generated by some kinds of keystrokes (such as pressing Enter in a modal dialog box).
The VCL has three mouse events: OnMouseDown event, OnMouseMove event, and OnMouseUp event.
When a VCL application detects a mouse action, it calls whatever event handler you've defined for the corresponding event, passing five parameters. Use the information in those parameters to customize your responses to the events. The five parameters are as follows:
Indicates which mouse button was involved: mbLeft, mbMiddle, or mbRight | |
Indicates the state of the Alt, Ctrl, and Shift keys at the time of the mouse action | |
Most of the time, you need the coordinates returned in a mouse-event handler, but sometimes you also need to check Button to determine which mouse button caused the event.
Note: Delphi uses the same criteria as Microsoft Windows in determining which mouse button has been pressed. Thus, if you have switched the default "primary" and "secondary" mouse buttons (so that the right mouse button is now the primary button), clicking the primary (right) button will record mbLeft as the value of the Button parameter.
Whenever the user presses a button on the mouse, an OnMouseDown event goes to the object the pointer is over. The object can then respond to the event.
To respond to a mouse-down action, attach an event handler to the OnMouseDown event.
The VCL generates an empty handler for a mouse-down event on the form:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin end;
Here's code that displays some text at the point where the mouse button is pressed. It uses the X and Y parameters sent to the method, and calls the TextOut method of the canvas to display text there:
The following code displays the string 'Here!' at the location on a form clicked with the mouse:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Canvas.TextOut(X, Y, 'Here!'); { write text at (X, Y) }
end;
When the application runs, you can press the mouse button down with the mouse cursor on the form and have the string, "Here!" appear at the point clicked. This code sets the current drawing position to the coordinates where the user presses the button:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Canvas.MoveTo(X, Y); { set pen position }
end;
Pressing the mouse button now sets the pen position, setting the line's starting point. To draw a line to the point where the user releases the button, you need to respond to a mouse-up event.
An OnMouseUp event occurs whenever the user releases a mouse button. The event usually goes to the object the mouse cursor is over when the user presses the button, which is not necessarily the same object the cursor is over when the button is released. This enables you, for example, to draw a line as if it extended beyond the border of the form.
To respond to mouse-up actions, define a handler for the OnMouseUp event.
Here's a simple OnMouseUp event handler that draws a line to the point of the mouse-button release:
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Canvas.LineTo(X, Y); { draw line from PenPos to (X, Y) }
end;
This code lets a user draw lines by clicking, dragging, and releasing. In this case, the user cannot see the line until the mouse button is released.
An OnMouseMove event occurs periodically when the user moves the mouse. The event goes to the object that was under the mouse pointer when the user pressed the button. This allows you to give the user some intermediate feedback by drawing temporary lines while the mouse moves.
To respond to mouse movements, define an event handler for the OnMouseMove event. This example uses mouse-move events to draw intermediate shapes on a form while the user holds down the mouse button, thus providing some feedback to the user. The OnMouseMove event handler draws a line on a form to the location of the OnMouseMove event:
procedure TForm1.FormMouseMove(Sender: TObject;Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Canvas.LineTo(X, Y); { draw line to current position }
end;
With this code, moving the mouse over the form causes drawing to follow the mouse, even before the mouse button is pressed.
Mouse-move events occur even when you haven't pressed the mouse button.
If you want to track whether there is a mouse button pressed, you need to add an object field to the form object.
To track whether a mouse button was pressed, you must add an object field to the form object. When you add a component to a form, Delphi adds a field that represents that component to the form object, so that you can refer to the component by the name of its field. You can also add your own fields to forms by editing the type declaration in the form unit's header file.
In the following example, the form needs to track whether the user has pressed a mouse button. To do that, it adds a Boolean field and sets its value when the user presses the mouse button.
To add a field to an object, edit the object's type definition, specifying the field identifier and type after the public directive at the bottom of the declaration.
Delphi "owns" any declarations before the public directive: that's where it puts the fields that represent controls and the methods that respond to events.
The following code gives a form a field called Drawing of type Boolean, in the form object's declaration. It also adds two fields to store points Origin and MovePt of typeTPoint.
type
TForm1 = class(TForm)
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
public
Drawing: Boolean; { field to track whether button was pressed }
Origin, MovePt: TPoint; { fields to store points }
end;
When you have a Drawing field to track whether to draw, set it to True when the user presses the mouse button, and False when the user releases it:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Drawing := True; { set the Drawing flag }
Canvas.MoveTo(X, Y);
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Canvas.LineTo(X, Y);
Drawing := False; { clear the Drawing flag }
end;
Then you can modify the OnMouseMove event handler to draw only when Drawing is True:
procedure TForm1.FormMouseMove(Sender: TObject;Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Drawing then { only draw if Drawing flag is set }
Canvas.LineTo(X, Y);
end;
This results in drawing only between the mouse-down and mouse-up events, but you still get a scribbled line that tracks the mouse movements instead of a straight line.
The problem is that each time you move the mouse, the mouse-move event handler calls LineTo, which moves the pen position, so by the time you release the button, you've lost the point where the straight line was supposed to start.
With fields in place to track various points, you can refine an application's line drawing.
When drawing lines, track the point where the line starts with the Origin field.
Origin must be set to the point where the mouse-down event occurs, so the mouse-up event handler can use Origin to place the beginning of the line, as in this code:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Drawing := True;
Canvas.MoveTo(X, Y);
Origin := Point(X, Y); { record where the line starts }
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Canvas.MoveTo(Origin.X, Origin.Y); { move pen to starting point }
Canvas.LineTo(X, Y);
Drawing := False;
end;
Those changes get the application to draw the final line again, but they do not draw any intermediate actions--the application does not yet support "rubber banding."
The problem with this example as the OnMouseMove event handler is currently written is that it draws the line to the current mouse position from the last mouse position, not from the original position.You can correct this by moving the drawing position to the origin point, then drawing to the current point:
procedure TForm1.FormMouseMove(Sender: TObject;Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Drawing then
begin
Canvas.MoveTo(Origin.X, Origin.Y); { move pen to starting point }
Canvas.LineTo(X, Y);
end;
end;
The above tracks the current mouse position, but the intermediate lines do not go away, so you can hardly see the final line. The example needs to erase each line before drawing the next one, by keeping track of where the previous one was. The MovePt field allows you to do this.
MovePt must be set to the endpoint of each intermediate line, so you can use MovePt and Origin to erase that line the next time a line is drawn:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Drawing := True;
Canvas.MoveTo(X, Y);
Origin := Point(X, Y);
MovePt := Point(X, Y); { keep track of where this move was }
end;
procedure TForm1.FormMouseMove(Sender: TObject;Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Drawing then
begin
Canvas.Pen.Mode := pmNotXor; { use XOR mode to draw/erase }
Canvas.MoveTo(Origin.X, Origin.Y); { move pen back to origin }
Canvas.LineTo(MovePt.X, MovePt.Y); { erase the old line }
Canvas.MoveTo(Origin.X, Origin.Y); { start at origin again }
Canvas.LineTo(X, Y); { draw the new line }
end;
MovePt := Point(X, Y); { record point for next move }
Canvas.Pen.Mode := pmCopy;
end;
Now you get a "rubber band" effect when you draw the line. By changing the pen's mode to pmNotXor, you have it combine your line with the background pixels. When you go to erase the line, you're actually setting the pixels back to the way they were. By changing the pen mode back to pmCopy (its default value) after drawing the lines, you ensure that the pen is ready to do its final drawing when you release the mouse button.
Delphi allows you to add multimedia components to your applications. To do this, you can use either the TAnimate component on the Win32 page or the TMediaplayer component on the System page of the Component palette. Use the animate component when you want to add silent video clips to your application. Use the media player component when you want to add audio and/or video clips to an application.
For more information on TAnimate and TMediaplayer components, see the VCL on-line help.
The following topics are discussed in this section:
The animation control in Delphi allows you to add silent video clips to your application.
To add a silent video clip to an application:
Always work directly with the Object Inspector when setting design time properties and creating event handlers.
This loads the AVI file into memory. If you want to display the first frame of the AVI clip on-screen until it is played using the Active property or the Play method, then set the Open property to True.
Note: If you make any changes to the form or any of the components on the form after setting Active to True, the Active property becomes False and you have to reset it to True. Do this either just before runtime or at runtime.
Suppose you want to display an animated logo as the first screen that appears when your application starts. After the logo finishes playing the screen disappears.
To run this example, create a new project and save the Unit1.pas file as Frmlogo.pas and save the Project1.dpr file as Logo.dpr. Then:
Logo1.Active := True;
LogoForm1.Close;
The media player component in Delphi allows you to add audio and/or video clips to your application. It opens a media device and plays, stops, pauses, records, etc., the audio and/or video clips used by the media device. The media device may be hardware or software.
To add an audio and/or video clip to an application:
Always work directly with the Object Inspector when setting design time properties and creating event handlers.
The multimedia device is played, paused, stopped, and so on when the user clicks the corresponding button on the mediaplayer component. The device can also be controlled by the methods that correspond to the buttons (Play, Pause, Stop, Next, Previous, and so on).
If you want the media player to be invisible at runtime, set the Visible property to False and control the device by calling the appropriate methods (Play, Pause, Stop, Next, Previous, Step, Back, Start Recording, Eject).
This example runs an AVI video clip of a multimedia advertisement for Delphi. To run this example, create a new project and save the Unit1.pas file to FrmAd.pas and save the Project1.dpr file to DelphiAd.dpr. Then:
Videoplayer1.Play;
pubsweb@inprise.com
Copyright © 1999, Inprise Corporation. All rights reserved.