Printer Projects
Printer Demo #2  Lab Report

Printing in Delphi: Brush Styles, Pen Styles, Shades of Gray
Scanned Image of 8.5"-by-11" Printout from Epson Color Stylus 850
Scanned Image of 8.5"-by-11"Printout from HP LaserJet 5
  
Note:  Aliasing  artifacts are caused in the above images by scanning and reduction.

Purpose
The purposes of this project are to

Materials and Equipment

Software Requirements
Windows 95/98/2000
Delphi 3/4/5 (to recompile)
PrinterDemo2.EXE

Hardware Requirements
VGA display
Windows printer

Procedure

  1. Double click on the PrinterDemo2.EXE icon to start the program.
  2. Press the TForm button to display the Demo directly on the canvas of the form. Minimize the form. Click on button on Windows 95/98 Tool Bar to reactivate the program. Notice anything drawn on the form is lost when the form is repainted.  There is NO persistence when drawing directly on the form's canvas.
  3. Press the OnPaint checkbox to again view the graphics drawn on the form.   As in Item 2 above, minimize the form and then click on its icon on the tool bar to reactivate it.  Note that when using the OnPaint the drawing has persistence.
  4. Maximize the form and/or drag the corners of the Windows to resize it.  Note the OnPaint fills the complete form. 
  5. Press the Clear button to clear the form.
  6. Press on the TImage button to draw directly on a TImage.Canvas. Notice that this image is correctly redrawn when the form is minimized and reactivated -- a TImage automatically provides persistence. (If desired, uncheck the visible checkbox to hide this TImage.)

ScreenPrinterDemo2C.jpg (31075 bytes)

  1. Maximize the form and/or drag the corners of the Windows to resize it.  Note the OnSize handler adjusts the size of the TImage but does not adjust the size of the buttons or checkboxes.
  2. Press either the Print Portrait or Print Landscape buttons. Select OK to the "Print Complete" notification.  (See sample output at the top of this page.)
  3. Select the Write to File Button to inspect the graphical output in an 800-by-600 BMP file, PrinterDemo2.BMP.
  4. Check the Clipping checkbox and press any of the buttons (TForm, TImage, Print Portrait, Print Landscape, Write to File) to see how a Windows region can be used to define a compound clipping area of a rectangle and an ellipse.  All of the graphics shown above are drawn but are clipped by the complex clipping area defined by a Windows region.

ScreenPrinterDemo2D.jpg (23876 bytes)

  1. Repeat on a variety of Windows printers and compare results.

Data

Printer Type Comments/Features
Canon BJC-620 Color ink jet Good color.OK with regular paper. Line.Styles only work for Pen.Width = 1. Approximately 3.5 "dashes" per inch for psDash Pen.Style.
Epson Stylus Color 850 Color ink jet Excellent color, especially with photo-quality paper. "Shades of gray" have slight blue-green hue on photo-quality paper, but OK with regular paper. Pen.Styles only work for Pen.Width = 1. Approximately 6 "dashes" per inch for psDash Pen.Style.
HP DeskJet 540 Color ink jet Has terrible color and does not support Pen.Style with width greater then 1.  The pie at 9 o'clock is does not print with color enabled, but does print a light dot pattern when the driver is forced to black mode. [Thanks to Jeff Wormsley for this information]
HP DeskJet 870C Color ink jet Pen.Styles are not very distinctive in black (they all appear to be the same) and are all psSolid for red. Background colors are wrong for most Brush.Styles, except for the first two rows in the output.
HP DesignJet 750C Color ink jet
"plotter"
Excellent color. Only known printing device to support Pen.Styles for Pen.Widths > 1.
HP LaserJet II Black/ White laser Does not print the bottom line, but otherwise is similar to the HP III. [Thanks to Jeff Wormsley for this information]
HP LaserJet IIP Black/ White laser Approximately 6 "dashes" per inch for psDash Pen.Style (which is much more readable than the HP LaserJet 5). Approximately 38 "dots" per inch for psDot Pen.Style (which is not the solid line seen with a HP LaserJet 5).
HP LaserJet III Black/ White laser Approximately 10 "dashes" per inch for psDash Pen.Style (which is much more readable than the HP LaserJet 5). Approximately 38 "dots" per inch for psDot Pen.Style (which is not the solid line seen with a HP LaserJet 5).
HP LaserJet 5 Black/ White laser "Shades of gray" are in groups of 4. For example, RGB(i,i,i) for i = 252..255 are the same, 248..251 are the same, etc. Pen.Styles are not very distinctive. Approximately 26 "dashes" per inch for psDash Pen.Style! Line for psDot Pen.Style appears as a solid line. The first two rows of Brush.Style appear the same. Most of the colors (the columns) cannot be distinguished with the various Brush.Styles.
IBM 4039 Black/ White laser  
Lexmark 4039 Plus Black/ White laser  

Discussion
The System colors were defined in a CONST array:

CONST
  //                      GGBBRR
  clMoneyGreen  = TColor($C0DCC0);    // additional colors that don't dither
  clSkyBlue     = TColor($F0CAA6);
  clCream       = TColor($F0FBFF);
  clMediumGray  = TColor($A4A0A0);
  ColorCount =  20;    // These colors won't dither on 256-color display in Windows
  colors :  ARRAY[0..ColorCount-1] OF TColor =
               (clBlack, clMaroon, clGreen, clOlive, clNavy, clPurple,
                clTeal, clSilver,
                clMoneyGreen, clSkyBlue, clCream, clMediumGray,
                clGray, clRed, clLime, clYellow, clBlue, clFuchsia,
                clAqua, clWhite);                 

The approach in this project was to define a single routine, DisplayGraphics, that draws on any canvas.  In this routine, the Clipping parameter determines when a clipping region is imposed on subsequent drawings.

When requested, a complex Windows region is defined to establish this clipping area.  CreateRectRgn and CreateEllipticRgn define two Windows "regions"   that are combined into a single region using CombineRgnSelectClipRgn then establishes the combined, complex region as the clipping region.  Note:   since Delphi has no "wrappers" around Windows hRGN objects, you must take care to free these resources using DeleteObject.

PROCEDURE DisplayGraphics(CONST Title:  STRING;
                          CONST Canvas:  TCanvas;
                          CONST Width, Height:  INTEGER;
                          CONST Clipping:  BOOLEAN);
  VAR
    brush         :  WORD;
    color         :  WORD;
    GrayShade     :  WORD;
    PenWidth      :  WORD;
    Region1       :  hRGN;
    Region2       :  hRGN;
    s             :  STRING;
    style         :  WORD;
    xBase         :  WORD;
    yBase         :  WORD;
    xDelta        :  WORD;
    yDelta        :  WORD;
    x             :  WORD;
    x1,x2         :  WORD;
    y             :  WORD;
BEGIN
  Region1 := 0;  // avoid compiler initialization warning
  // Demonstrate Clipping by the union of two rectangular regions
  IF   Clipping
  THEN BEGIN
    // Create rectangular region of the upper-half of the vertical area,
    // from 20% to 80% of the horizontal area.
    Region1 := CreateRectRgn(Width DIV 5,
                             0,
                             4*Width DIV 5,
                             Height DIV 2);
    // Create elliptical region of the lower-half of the vertical area
    Region2 := CreateEllipticRgn(Width   DIV 4,
                                 Height  DIV 2,
                                 3*Width DIV 4,
                                 Height);
    TRY
      // Add Region 2 to Region 1
      CombineRgn(Region1, Region1, Region2, RGN_OR);
      SelectClipRgn(Canvas.Handle, Region1);
      // Show Outline of Clipping Area
      Canvas.Brush.Color := clRed;
      FrameRgn(Canvas.Handle, Region1, Canvas.Brush.Handle, 2,2);
    FINALLY
      DeleteObject(Region2)
    END
    // See final DeleteObject statement at end of routine
  END;

A big red X formed by diagonals is drawn, as is a box around the edges:

  // Big X
  Canvas.Pen.Color := clRed;
  Canvas.MoveTo (      0,        0);
  Canvas.LineTo (Width-1, Height-1);
  Canvas.MoveTo (      0, Height-1);
  Canvas.LineTo (Width-1,        0);
  // A Box -- the hard way
  Canvas.Pen.Color := clBlack;
  Canvas.MoveTo (      0,        0);
  Canvas.LineTo (      0, Height-1);
  Canvas.LineTo (Width-1, Height-1);
  Canvas.LineTo (Width-1,        0);
  Canvas.LineTo (      0,        0);

Use something like MulDiv(Width, percent, 100) to position to a particular horizontal location in a device-independent way. Likewise, use something like MulDiv(Height, percent, 100) to position to a particular vertical location in a device-independent way.

Set Font.Height (instead of Font.Size) for better device independence in the display of text.  In fact, it's a good idea to never use Font.Size because of various bugs in printer drivers.  Set the Height in a device-independent way and never use the font.size:

Printer.Canvas.Font.Height :=
MulDiv(GetDeviceCaps(Printer.Canvas.Handle, LOGPIXELSY), FontSize, 72);

  // Line Styles
  yDelta := MulDiv(Height, 125, 10000);   // 1.25%
  Canvas.Font.Height := MulDiv(Height, 2, 100);
  Canvas.Font.Name   := 'Arial';
  Canvas.Font.Color  := clBLack;
  Canvas.Font.Style  := [fsItalic, fsbold];
  Canvas.Brush.Style := bsClear;
  s := 'efg''s Computer Lab:  Printer Demo #2';
  Canvas.TextOut((Width - Canvas.TextWidth(s)) DIV 2,
                 MulDiv(Height, 1, 100),
                 s);
  Canvas.Font.Name   := 'Times New Roman';
  Canvas.Font.Style  := [];
  Canvas.Font.Height := MulDiv(Height, 15, 1000);
  s := 'www.efg2.com/Lab';
  Canvas.TextOut((Width - Canvas.TextWidth(s)) DIV 2,
                 MulDiv(Height, 3, 100),
                 s);
  Canvas.Font.Height := MulDiv(Height, 2, 100);
  Canvas.Font.Color  := clBlue;
  Canvas.Font.Height := MulDiv(Height, 1, 100);
  s := 'Line Styles (by pen width)';
  Canvas.TextOut(MulDiv(Width, 76, 100),
                 MulDiv(Height, 7, 100) - 3*Canvas.TextHeight(s) DIV 2,
                 s);

The various Pen.Styles are set by typecasting an integer, namely, TPenStyle(style MOD 7) returned the various Pen.Styles:   psSolid (0), psDash (1), psDot (2), psDashDot (3), psDashDotDot (4), psClear (5), and psInsideFrame (6).

  Canvas.Pen.Color := clBlack;
  FOR PenWidth := 1 TO 4 DO
  BEGIN
    FOR style := 0 TO 6 DO
    BEGIN
      // Style is
      // psSolid, psDash, psDot, psDashDot, psDashDotDot, psClear, psInsideFrame
      Canvas.Pen.Style := TPenStyle(style MOD 7);
      Canvas.Pen.Width := PenWidth;
      y := MulDiv(Height, 7, 100) + (10 * (PenWidth-1) + style)*yDelta;
      Canvas.MoveTo ( MulDiv(Width, 76, 100), y);
      Canvas.LineTo ( MulDiv(Width, 87, 100), y)
    END
  END;
  Canvas.Pen.Color := clRed;
  FOR PenWidth := 1 TO 4 DO
  BEGIN
    FOR style := 0 TO 6 DO
    BEGIN
      // Style is
      // psSolid, psDash, psDot, psDashDot, psDashDotDot, psClear, psInsideFrame
      Canvas.Pen.Style := TPenStyle(style MOD 7);
      Canvas.Pen.Width := PenWidth;
      y := MulDiv(Height, 7, 100) + (10 * (PenWidth-1) + style)*yDelta;
      Canvas.MoveTo ( MulDiv(Width, 88, 100), y);
      Canvas.LineTo ( MulDiv(Width, 99, 100), y)
    END
  END;

Draw the color boxes with the various brush styles:

  // Rectangles:  Vary Colors and Brush Styles
  Canvas.Pen.Style := psInsideFrame;
  Canvas.Pen.Color := clBlack;
  Canvas.Pen.Width := 1;
  xDelta := MulDiv(Width,  35, 1000);   // 3.5%
  yDelta := MulDiv(Height, 50, 1000);   // 5%
  xBase := MulDiv(Width, 2, 100);       //  2%
  yBase := MulDiv(Height,7, 100);       //  7%
  s := 'Brush Styles';
  Canvas.TextOut(XBase, YBase - 3*Canvas.TextHeight(s) DIV 2,
                 s);
  FOR color := Low(Colors) TO High(Colors) DO
  BEGIN
    x := xBase + color*xDelta;
    FOR brush := 0 TO 7 DO  {See p. 764, VCL Ref, Delphi 1}
    BEGIN
      // Per E-mail from Thérèse Hanquet, always assign Brush Color
      // before assigning Brush Style.  March 2001.
      Canvas.Brush.Color := colors[color];
      Canvas.Brush.Style := TBrushStyle(brush);
     
      y := yBase + brush*yDelta;
      Canvas.Rectangle (x, y, x+xDelta, y+yDelta);
    END;
  END;

The various Brush.Styles are set by typecasting an integer, namely TBrushStyle(brush) returned the various brush styles:  bsSolid (0), bsClear (1), bsHorizontal (2), bsVertical (3), bsFDiagonal (4), bsBDiagonal (5), bsCross (6), bsDiagCross (7).

Here's what these rectangles with various colors and brush styles look like:

If the order of the assignments to Brush.Color and Brush.Style were reversed in the code above, the bsClear row above (the second row), would also be a solid color (like the first row).  Perhaps this is a bug.  [Thank you, Thérèse Hanquet, for bringing this to my attention. This has affected this example since D1!  efg, March 2001.]

The two rows of shades-of-gray boxes are created with this code.  The function call, RGB(i, i, i), for i = 0..255 gives the possible 256 shades of "gray" colors:

  // Rectangles:  Shades of Gray
  yBase  :=  MulDiv(Height, 87, 100);
  yDelta :=  MulDiv(Height,  4, 100);
  s := 'Shades of Gray';
  Canvas.TextOut(MulDiv(Width, 1, 63),
                 YBase - 3*Canvas.TextHeight(s) DIV 2,
                 s);
  Canvas.Brush.Style := bsSolid;
  FOR GrayShade := 0 TO 60 DO
  BEGIN
    x1 := MulDiv(Width, GrayShade+1, 63);
    x2 := MulDiv(Width, GrayShade+2, 63);
    Canvas.Brush.Color := RGB(4*GrayShade, 4*GrayShade, 4*GrayShade);
    Canvas.Rectangle (x1, yBase, x2, yBase+yDelta);
  END;
  yBase := yBase + yDelta;
  FOR GrayShade := 0 TO 60 DO
  BEGIN
    x1 := MulDiv(Width, GrayShade+1,   63);
    x2 := MulDiv(Width, GrayShade+2, 63);
    Canvas.Brush.Color := RGB(195+GrayShade, 195+GrayShade, 195+GrayShade);
    Canvas.Rectangle (x1, yBase, x2, yBase+yDelta);
  END;

Ellipses, chords and pies are drawn next:

  // Ellipses
  Canvas.Pen.Color := clWhite;
  Canvas.Brush.Style := bsSolid;
  Canvas.Brush.Color := clRed;
  Canvas.Ellipse (MulDiv(Width,  55, 100),
                  MulDiv(Height, 55, 100),
                  MulDiv(Width,  85, 100),
                  MulDiv(Height, 85, 100));
  Canvas.Brush.Color := clLime;
  Canvas.Ellipse (MulDiv(Width,  57, 100),
                  MulDiv(Height, 57, 100),
                  MulDiv(Width,  83, 100),
                  MulDiv(Height, 83, 100));
  Canvas.Brush.Color := clBlue;
  Canvas.Ellipse (MulDiv(Width,  59, 100),
                  MulDiv(Height, 59, 100),
                  MulDiv(Width,  81, 100),
                  MulDiv(Height, 81, 100));
  Canvas.Brush.Color := clYellow;
  Canvas.Ellipse (MulDiv(Width,  61, 100),
                  MulDiv(Height, 61, 100),
                  MulDiv(Width,  79, 100),
                  MulDiv(Height, 79, 100));
  Canvas.Brush.Color := clWHite;
  Canvas.Ellipse (MulDiv(Width,  63, 100),
                  MulDiv(Height, 63, 100),
                  MulDiv(Width,  77, 100),
                  MulDiv(Height, 77, 100));
  // Chords
  Canvas.Pen.Color := clRed;
  Canvas.Chord (MulDiv(Width,  63, 100),
                MulDiv(Height, 63, 100),
                MulDiv(Width,  77, 100),
                MulDiv(Height, 77, 100),
                MulDiv(Width,  72, 100),
                MulDiv(Height, 63, 100),
                MulDiv(Width,  72, 100),
                MulDiv(Height, 77, 100));
  Canvas.Pen.Color := clBlue;
  Canvas.Chord (MulDiv(Width,  63, 100),
                MulDiv(Height, 63, 100),
                MulDiv(Width,  77, 100),
                MulDiv(Height, 77, 100),
                MulDiv(Width,  75, 100),
                MulDiv(Height, 77, 100),
                MulDiv(Width,  75, 100),
                MulDiv(Height, 63, 100));
  // Pie
  Canvas.Pen.Color := clWhite;
  Canvas.Brush.Style := bsSolid;
  Canvas.Brush.Color := clLime;
  Canvas.Pie (MulDiv(Width,  15, 100),
              MulDiv(Height, 55, 100),
              MulDiv(Width,  45, 100),
              MulDiv(Height, 85, 100),
              MulDiv(Width,  45, 100),
              MulDiv(Height, 55, 100),
              MulDiv(Width,  15, 100),
              MulDiv(Height, 55, 100));
  Canvas.Brush.Color := clCream;
  Canvas.Pie (MulDiv(Width,  15, 100),
              MulDiv(Height, 55, 100),
              MulDiv(Width,  45, 100),
              MulDiv(Height, 85, 100),
              MulDiv(Width,  15, 100),
              MulDiv(Height, 55, 100),
              MulDiv(Width,  15, 100),
              MulDiv(Height, 85, 100));
  Canvas.Brush.Color := clAqua;
  Canvas.Pie (MulDiv(Width,  15, 100),
              MulDiv(Height, 55, 100),
              MulDiv(Width,  45, 100),
              MulDiv(Height, 85, 100),
              MulDiv(Width,  45, 100),
              MulDiv(Height, 85, 100),
              MulDiv(Width,  45, 100),
              MulDiv(Height, 55, 100));
  Canvas.Brush.Color := clMoneyGreen;
  Canvas.Pie (MulDiv(Width,  15, 100),
              MulDiv(Height, 55, 100),
              MulDiv(Width,  45, 100),
              MulDiv(Height, 85, 100),
              MulDiv(Width,  15, 100),
              MulDiv(Height, 85, 100),
              MulDiv(Width,  45, 100),
              MulDiv(Height, 85, 100));                                         

The last part of DisplayGraphics prints a footer with the date and printer name, and does some housekeeping to recycle the clipping region resource.

  // Footer
  Canvas.Font.Height := MulDiv(Height, 1, 100);
  Canvas.Font.Color := clBlack;
  Canvas.Brush.Style := bsClear;
  s := Title + ', ' + IntToStr(Width)  + ' pixels wide by ' +
                 IntToStr(Height) + ' pixels high, ' +
                 FormatDateTime('m/d/yy hh:nn', Now);
  Canvas.TextOut ((Width  - Canvas.TextWidth(s)) DIV 2,
                  Height - 3*Canvas.TextHeight(s) DIV 2, s);
  IF   Clipping
  THEN BEGIN
    SelectClipRgn(Canvas.Handle, 0);  // remove clipping restriction
    DeleteObject(Region1)
  END
END {DisplayGraphics};

DisplayGraphics is called with various parameters for each kind of target canvas.  Simply drawing these figures on a form's canvas is NOT a good idea since there is no persistence.  If you minimize the form, or display a window over the objects drawn on the form, they original drawings will be lost.

TForm

procedure TFormPrinterDemo2.ButtonTFormClick(Sender: TObject);
begin
  DisplayGraphics ('TForm',
                   Self.Canvas,
                   Self.ClientWidth,
                   Self.ClientHeight,
                   CheckBoxClipping.Checked)
end;   

Only draw on a form's canvas inside an OnPaint handler.   Click the check box to "hook up" the FormPaint:

procedure TFormPrinterDemo2.CheckBoxOnPaintClick(Sender: TObject);
begin
  IF   CheckBoxOnPaint.Checked
  THEN Self.OnPaint := Self.FormPaint
  ELSE Self.OnPaint := NIL;
  Self.Invalidate
end;  

With the FormPaint defined, you can resize the form however you'd like.   The form is redrawn by just calling the Invalidate method.   (See below for changes to Image1 here.)

procedure TFormPrinterDemo2.FormResize(Sender: TObject);
begin
 IF   CheckBoxOnPaint.Checked
 THEN Self.Invalidate;
 Image1.Width  := ClientWidth  - 2*Image1.Left;
 Image1.Height := ClientHeight - Image1.Top - Image1.Left;
 IF   ImageShow
 THEN ButtonTImageClick(Sender);
end;     

TImage

Keeping persistent graphics is much easier by displaying them in a TImage.   As shown next, it's better to draw on an in-memory bitmap the same size of the TImage to avoid flicker while drawing:

procedure TFormPrinterDemo2.ButtonTImageClick(Sender: TObject);
  VAR
    Bitmap:  TBitmap;
begin
  ImageShow := TRUE;
  Bitmap := TBitmap.Create;
  TRY
    Bitmap.Width  := Image1.Width;
    Bitmap.Height := Image1.Height;
    DisplayGraphics ('TBitmap/TImage',
                     Bitmap.Canvas,
                     Bitmap.Width,
                     Bitmap.Height,
                     CheckBoxClipping.Checked);
    Image1.Picture.Graphic := Bitmap;
  FINALLY
    Bitmap.Free
  END
end;   

The FormResize (see above) dynamically changes the size of the TImage and forces an update.  This approach automatically redraws the whole background (otherwise you'd be drawing on top of the previous drawing).

TPrinter

Drawing on the printer isn't much more complicated than a TForm or a TImage:

procedure TFormPrinterDemo2.ButtonPrintPortraitClick(Sender: TObject);
  VAR
    Title:  STRING;
begin
  Printer.Orientation := poPortrait;
  Printer.BeginDoc;
    IF   Printer.PrinterIndex < 0
    THEN Title := 'Printer (Portrait)'
    ELSE Title := 'Printer (Portrait, ' +
                  Printer.Printers.Strings[Printer.PrinterIndex] + ')';
    DisplayGraphics (Title,
                  Printer.Canvas, Printer.PageWidth, Printer.PageHeight,
                  CheckBoxClipping.Checked);
  Printer.EndDoc;
  ShowMessage('Printer Demo #2 Printed (Portrait)');
end;       

A similar routine is used to display the drawings in the landscape orientation.

TBitmap

Drawing only on an in-memory bitmap and saving it to a disk file is fairly simple:

procedure TFormPrinterDemo2.ButtonWriteFileClick(Sender: TObject);
  VAR
    Bitmap:  TBitmap;
begin
  Bitmap := TBitmap.Create;
  TRY
    Bitmap.Width  := 800;
    Bitmap.Height := 600;
    Bitmap.PixelFormat := pf24bit;
    DisplayGraphics ('TBitmap',
                     Bitmap.Canvas,
                     Bitmap.Width,
                     Bitmap.Height,
                     CheckBoxClipping.Checked);
    Bitmap.SaveToFile('ScreenDemo2.BMP');
  FINALLY
    Bitmap.Free
  END
end;        

Miscellaneous

Steve Schafer's  (Team B) UseNet Post:  "GDI drivers are not required to support line styles other than psSolid and psClear if the line width is anything more than 1 pixel.  Some drivers do support wide styled lines, but most do not. The only sure-fire fix that I know of is to draw all of the dashes and dots yourself."

Conclusions
Using device independence drawing allows one to draw on any canvas, whether the canvas is for a TImage, a TPrinter, or even an in-memory TBitmap.

Drawing a line with various values for Pen.Style is possible for Pen.Widths > 1, but most printers do not support this.

Getting the "same" printout on a variety of Windows printers is almost impossible! (This is a Windows issue, not a Delphi problem.   See Steve Schafer's UseNet Post about why WYSIWIG is so difficult.)


Keywords
Windows non-dither colors, Pen.Style, Pen.Width, Brush.Style, Canvas.MoveTo, Canvas.LineTo, Canvas.Rectangle, Canvas.Ellipse, Canvas.Chord, Canvas.Pie, Canvas.TextOut, MulDiv, Device-independent graphics, CreateRectRgn, CreateEllipticRgn, CombineRgn, SelectClipRgn, DeleteObject

Download
Delphi 3/4/5 Source and EXE (127 KB): PrinterDemo2.ZIP


Delphi Conversion Notes
No changes are needed to the Delphi 3 code to compile in Delphi 4 or 5.
Size of Delphi 3 EXE:  229 KB
Size of Delphi 4 EXE:  317 KB
Size of Delphi 5 EXE:  330 KB

If you use short filenames (PDemo2.dpr, SPDemo2.pas, etc.) and change the // comments to (* *) or { } comments, then the project will compile and run correctly in all versions of Delphi (D1 - D5).  Of course, it only takes a few minutes to change it over anyway.  [Thanks to Jeff Wormsley for this comment.]


Updated 18 Feb 2002


since 1 Nov 1998