TExcellent FAQ Version 0A.02

Copyright © 1989-2010 by Joe C. Hecht All Rights Reserved
Copyright © 2011-2017 by CODE4SALE, LLC All Rights Reserved

Contact CODE4SALE, LLC - Joe Hecht.

TExcellent home page

TExcellent documentation home page!

Try TExcellentFormPrinter!   Buy TExcellentFormPrinter!
Try TExcellentImagePrinter!   Buy TExcellentImagePrinter!

Product names, trademarks, and servicemarks mentioned are owned by their respective owners.


FAQ

Your spelling is terrible!

How do I install the product?

What components work with TExcellentFormPrinter?

How can I improve the reliability of printing from my application?

Why does the TExcellentDemo have more options than TExcellentExample?

Can I print VCL forms and images to devices contexts other than a printer?

How can I reduce the print job size?

Whats the deal with uneven printer margins?

Can I use different Windows mapping modes?

Help! My printouts are still blank, garbled, or only partially print!

Help! My Xerox WorkCentre does not print from Delphi and C++Builder!

TExcellent prints lines across the printout!

Can I print other things on the page in addition to my form?

How can I print VCL forms without displaying them on the screen?

Can TExcellentFormPrinter print controls instead of the form?

Help! My Woll2Woll TwwDBRichEdit does not print!

Help! My SpeedButton does not print in the down position!

Help! My Async Terminal does not print!

What happened to the debug variables in TExcellentProducts?

What happened with the short circuit boolean evaluation problem?

Help! I want to scale my bitmap to fit...!

Help! I want to scale my form to fit...!


Your spelling is terrible!

Yes, you are correct! I wish I had the time to fix all the errors, but it was a toss up between delivering quality products and support, or pleasing everyone that has an eye for a typo. Perhaps we will get this fixed in the next version :)

Back to the FAQ


How do I install the product?

The units provide a 100 percent API interface, and do not install as components on the IDE component palette. For specific instructions, please see the TExcellentDocs.htm web page to go to the documentation page for the product you are using.

Back to the FAQ


What components work with TExcellentFormPrinter?

TExcellentFormPrinter should work with most all Delphi/BCB graphic controls. TExcellentFormPrinter also contains hooks that allow you to tap into the printing system and successfully print most problematic VCL controls! TExcellentFormPrinter has been tested to work with the following standard controls*:


TAnimate
TBdChart
TBevel
TBitBtn
TButton
TCalendar
TCCalendar
TCColorGrid
TCDirectoryOutline
TCGauge
TChart
TCheckBox
TCheckListBox
TColorGrid
TComboBox
TCoolBar
TCSpinButton
TCSpinEdit
TDateTimePicker
TDBCheckBox
TDBComboBox
TDBCtrlGrid
TDBEdit
TDBGrid
TDBImage
TDBListBox
TDBLookupCombo
TDBLookupComboBox
TDBLookupList
TDBLookupListBox
TDBMemo
TDBNavigator
TDBRadioGroup
TDBRichEdit
TDBText
TDirectoryListBox
TDirectoryOutline
TDrawGrid
TDriveComboBox
TEdit
TFileListBox
TFilterComboBox
TGauge
TGroupBox
THeader
THeaderControl
THotKey
TImage
TLabel
TListBox
TListView
TMaskEdit
TMediaPlayer
TMemo
TMonthCalendar
TNotebook
TOleContainer
TOutline
TPageControl
TPaintBox
TPanel
TPerformanceGraph
TPie
TProgressBar
TRadioButton
TRadioGroup
TRichEdit
TScrollBar
TScrollBox
TShape
TSpeedButton
TSpinButton
TSpinEdit
TSplitter
TStaticText
TStatusBar
TStringGrid
TTabbedNotebook
TTabControl
TTabSet
TTabSheet
TToolBar
TToolButton
TTrackBar
TTreeView
TUpDown
TVtChart


The following controls have not tested:


TDecisionCube



The following controls do not currently print correctly:


TControlBar
TPageScroller



The following 3rd party controls do not currently print correctly:


TChartfx
TGraph
THTML
TWebBrowser
TF1Book
TPdf

* Not an exhaustive test. Standard controls do not include 3rd party controls shiped with Delphi and CBuilder.

Please use the trial version to verify that TExcellentFormPrinter meets your printing needs!

Back to the FAQ


How can I improve the reliability of printing from my application?

Most of us have run into applications that fail to print on one printer or another. As a former support engineer, I have certainly had my share of calls that start out with "well it prints from my paint applicaiton"!

The truth is that different applications use different methods to print raster images. Most all of these applications succeed on most printers, and all will fail on some printers. Printing failures are just part of the business, and it is up to *you* to decide how important it is to support a print driver that is uncorporative. Since you are here, you have probably already decided that it is of the upmost importance to support as many printers as possible, rather than face unhappy customers wanting to return your product for a refund.

Our TExcellent line of printing products go a long way to solve most problems associated with printing. In their default state, many have found that our products cure a great deal of all the failures associated with printing raster images, however, often times that is simply not enough! For this reason, we have included a small number of optional runtime switches to assist you in getting your application printing on the widest variety of printers possible.

Our TExcellentDemo application demonstrates the use of these optional switches. While some programmers balk at the idea of adding these "band-aids" to their application, we believe that adding this functionality to your application is very effective insurance against product returns and unhappy customers!

It is up to you to decide how to implement the interface for switching the options on and off. Certainly some will feel that the user deserves a small dialog box to record the setup, while others will want to hide these switches deep in the windows registry, and save them for a rainy day.

However you decide to record and set these switches, we certainly recommend that you use them. After all, the goal here is to get your application printing for your user, rather than receive a product return!

Here are our recommendations in order of priorty:

1) Allow for the use of DDB printing. The TExcellent line of printer products by default uses DIB printing and calls the Windows StretchDIBits() function. While this should always work, if the call fails, we automatically drop back to using DDB printing and call BitBlt() or StretchBlt(). The reason why you should have a switch to force the use of DDB printing is that sometimes, a driver will claim that the StretchDIBits() call was successful, even though it failed. Of course, in this situation, our automatic recovery will not detect the failure, thus the need for a switch to force the use of DDB printing. The type of DDB printing we use is completely safe, as we use a DDB based on the printer's device context, not the screen. The function to use to force DDB printing is either the TExcellentImagePrinter PrnDIBSetDebugUseDDB() function, or the TExcellentFormPrinter PrnFormSetDebugUseDDB() function. Setting this option can often reduce the size of your print job dramatically. This option can be easily added to your application (please see the TExcellentExample application source code).

2) Allow Windows to stretch the output! The TExcellent line of printer products by default prints at the full resolution of the printing device. For some systems, the size of the print job can overwhelm the driver. You have two different switches to help with this, our DoWinScale switch, and our compress switch. Both have merits, and can be easily added to your application (please see the TExcellentExample application source code). For our DoWinScale switch, you will be interested in the the PrnDIBSetDoWinScale() function for TExcellentImagePrinter, and the PrnFormSetDoWinScale() function for TExcellentFormPrinter. If you are interested in adding support for our compress switch, you will be interested in the the PrnDIBSetOutputScaleFactor function for TExcellentImagePrinter, and the PrnFormOutputScaleFactor function for TExcellentFormPrinter. Again, these options can be easily added to your application (please see the TExcellentExample application source code).

3) Allow the use of SleepProc! This one is a bit tricky to implement, as it requires a small hack to the VCL's TPrinter unit. This hack will get your application printing on the Xerox WorkCentre line of printers, along with several other printers marketed under different brand names. We have seen much success with printers from Brother, Sharp, and Zenographcis. For instructions on using this patch, please see the following FAQ: Help! My Xerox WorkCentre does not print from Delphi and C++Builder!

4) In our TExcellentDemo application, you may have noticed options for "FullColorForms" and SleepBit. It's worth noting that neither of these options have ever been documented as useful, however in the interest of reliable printing, we included them, as we felt that the possibility exist that they will be usefull. The "FullColorForms" option simply turns off a color reduction technique we use in TExcellentFormPrinter and its use can be seen in the TExcellentExample application source code. The SleepBlt example is described the following FAQ: Help! My Xerox WorkCentre does not print from Delphi and C++Builder!

5) Many of our TExcellentImagePrinter customers had previously used StretchDIBits() to send images to the printer. Of course, this sometimes fails in the real world. Some of our customers really liked the way StretchDIBits() worked (fast and generally very efficient in terms of print job size), however they also needed reliability. Some of those customers have reported that they continue to use StretchDIBits(), and offer a user setting to switch over to using TExcellentImagePrinter when things start going south. We think this is a fine idea, and wholeheartedly embrace this approach! After all, the goal here is to print fast and print reliably!

Finally, we recommend that you read the following FAQs:
How can I improve the reliabitily of printing from my application?
Help! My printouts are still blank, garbled, or only partially print!
How can I reduce the print job size?

Back to the FAQ


Why does the TExcellentDemo have more options than TExcellentExample?

The TExcellentDemo uses a hack that we used for the Borland TPrinter unit. Since we are not able to ship the modified version of the TPrinter unit, we could not include it in the TExcellentExample source code.

We highly recommend using the hack, and instructions for implementing the hack is detailed in the section(s):

Help! My Xerox WorkCentre does not print from Delphi and C++Builder!
How can I improve the reliability of printing from my application?

Back to the FAQ


Can I print VCL forms and images to devices contexts other than a printer?

Absolutely! We do recommend that you print to a true memory dc (not a dc based on a DIBSection).

Back to the FAQ


How can I reduce the print job size?

We strive to produce the smallest possible print job size that is *reliably* possible. Please remember that TExcellentPrinter products use the full resolution of the printer to produce a quality print job.

We have found that changing the spooler settings in the printer control panel can often provide relief. "RAW" mode is usually preferrable, and usually results in much faster printing. There are exceptions, such as the Lexmark series that can use the PMJournal method of spooling, perform extremely well using this setting. Usually "EMF" spooling is the least desirable.

It is also worth noting that network printers are often installed incorrectly. We have found that many printers do not have official support for networking, however, the manufacturer may give some helpfull advice for networking a given printer. The Lexmark Z11 printer is a good example, in that networking this printer is not officially supported, however Lexmark provides some excellent advice for adding it to the network. In the case of the Lexmark Z11 printer, normal network installation techniques often result in failed or *VERY* large print jobs. Following the Lexmark advice to installing the drivers locally to each machine, then remapping the local port resulted in super small, fast printouts.

Finally, for our TExcellentImagePrinter product, if you want smaller print jobs from high color images, we recommend reducing the color depth of the images you are printing. This can easily be done by using the TJpeg component. Simply put, you assign a bitmap to a JPEG, set the compression quality to 100 percent, compress the jpeg, then decompress the jpeg with the TJPEGImage PixelFormat property to jf8Bit. You can then assign resulting image to an 8 bit bitmap. We cannot stress enough that you should step through the JPEG conversion code to make absolutely sure the JPEG was decompressed in 8 bit mode and was successfully tansfered to the bitmap with 8 bits of color depth. This can reduce the size of your print job by 3 times or more.

The following example details one way to accomplish color reduction:

var
  JP : TJPEGImage;
  BM : TBitmap;
begin
  Bm := TBitmap.Create;
  Bm.LoadFromFile('test.bmp');
  Jp := TJPEGImage.Create;
  Jp.Assign(bm);
  Jp.CompressionQuality := 100;
  Jp.SaveToFile('temp.jpg');
  Jp.Free;
  Jp := TJPEGImage.Create;
  Jp.PixelFormat := jf8Bit;
  Jp.LoadFromFile('temp.jpg');
  bm.PixelFormat := pf8bit;
  bm.Assign(Jp);
  bm.SaveToFile('temp.bmp');
  Jp.Free;
  bm.Free;
 {Load the temp bitmap for use with TExcellentImagePrinter}
end;

Finally, we recommend that you read the following FAQs:
How can I improve the reliability of printing from my application?
Help! My printouts are still blank, garbled, or only partially print!

Back to the FAQ


What's the deal with uneven printer margins?

Most printers are not capable of printing on the entire page area. In this case, there will be a small amount of space between the edge of the paper, and the beginning of the imagable area. We call this unprintable area the "margin". On most printers, there will be a margin on all four sides of the page (left, top, right, and bottom). Its worth noting that on most printers, the margin on one side of the page are not always equal with the margin on the opposite side. In other words, commonly, a printer will have a different size margin for the left and right sides, or the top and bottom side of the page. Usually, this difference is quite small and unnoticable, however, often enough, on some printers, there will be a significant difference that can be very noticeable if you have designed your page output to appear centered on the paper.

In our PrnUtils unit, we have added at GetPrnPageInfo() function to assist you in overcoming the problem of printers that have uneven margines. The GetPrnPageInfo() function obtains:

(a) The papersize that the printer is currently using.

(b) The imageable area that the printer is capable of printing.

(c) The left and top margins that the printer uses.

Given these values, we are able to calculate if the printer has uneven margins, and if so, we then calculate:

a) How much (if any) you would have to move (offset) where you to draw your output from the (left,top) of the imagable area to achieve even margins. We call this value the "AdjustedMarginOffset".

b) How much (if any) you would have to reduce the imagable area you can draw to achieve even margins. We call this reduced imagable area "AdjustedPageArea".

In general, for most printers, there is no way to tell the printer to physically adjust the margin and printable area to produce correct results, and it is up to your application to move (or offset) where you start your drawing output at, and how far you will allow your application to print to accommodate for uneven margins.

Note that there are two methods of moving (offset) where your drawing output appears on the page

1) You can offset the output by manually adding the AdjustedMarginOffset to your drawing command like this:
MoveTo(100 + AdjustedMarginOffset.x, 100 + AdjustedMarginOffset.y)

2) You can call the Delphi function MoveWindowOrg() to remap the canvas origin (where 0,0 is located) by the amount supplied in the function call.

Since there is no real way to reduce the imagable area you print to, you must make allowances in how far you allow your drawing to go. For additional safety, you can create a clipping region that would exclude any additional printing area that would exceed the area you have chosen to accommodate a print job that would have even margins.

If you are interested in printing with even margins, it is recommended that you take a look at the GetPrnPageInfo() function in the PrnUtils documentation. This function will return a TPrnPageInfo structure that details:

1) normal margins to printing area.

2) normal paper size.

3) normal imagable area.

4) adjusted margins (equal on all sizes).

5) the adjusted imagable area for equal margins.

6) the adjusted margin offset (amount to offset output for equal margins).

7) the pixels per inch of the device.

The following diagram details a printer with uneven margins, and the amounts necessary to compensate for the problem. For simplicity, only the margins in the "X" direction (left and right) are detailed. The dotted lines represent the printers normal margins and imagable area.

Margins

Back to the FAQ


Can I use different Windows mapping modes?

Yes! While TExcellentImagePrinter and TExcellentFormPrinter were designed to be used in MM_TEXT mode (where 1 unit = 1 pixel), if you use other mapping modes, simply use the Windows LPtoDP() function to convert coordinates in the mapping mode that you are using to device pixels, temporarily switch back to MM_TEXT mode, make your TExcellent call, then switch back to the mapping mode you were previously using.

Back to the FAQ


Help! My printouts are still blank, garbled, or only partially print!

TExcellentProducts are designed to overcome just about every problem associated with printing images to a graphics capible device, and the system works well with over 99 percent of the printers on the market. However, there are always exceptions.

Printing under Windows can be very complex. Function calls do not go directly to the print driver, and are routed through the GDI, the spooling system, and the print driver. Often, one or more of these components will off load some of the work to the video driver. This can sometimes make a very difficult job of tracking down a failure.

A good example is where printed output is pushed through the GDI, where it might get pushed out to the spooler. The spooler might hold the output back for a few seconds or even a few days (if the printer is offline). The spooler accepts the output, and the GDI reports back that the original function call succeeded. When the spooler eventually sends the output to the print driver, the print driver might decide to offload some of its work to the video driver (in the form of a memory DC). Note that the video driver is used to making bitmaps at screen resolutions, and may choke when the size of the memory DC the print driver tries to create is much larger, and the print job suddenly fails.

Please note that this is only one example of how a failure in the GDI, spooling system, print driver and or video driver might cause a failed print job. We have seen many other examples of failures.

After examining failures in the system, we can only do our best to prepare our printing output to take the least path of resistance by trying to avoid any and all issues that can cause a print job to fail. Unfortunately, this is not always enough to insure 100 percent reliability.

Before we wrote the printing engine for TExcellent products, we found that the number one cause of failure was simply the preparation and delivery of the printed output. This solved about 99 percent of all printing failures, and proved that in general, the print driver was not the cause of the problem. The final 1 percent of printing failures can be traced to either video driver problems, spooler problems, and finally, the print driver itself.

Overcoming the final 1 percent of printing failures:

1) We have found several instances where simply changing the color depth the video card is using will often time cure a printing problem. Most often, the printing failure will happen when the video card is running in 16 or 32 bit color mode, and switching to 24 bit color depth often fixes the problem. Note that the problem is not necessarily a video driver problem, but can sometimes be traced to the print driver. Sometimes, upgrading the video driver, or the print driver will help. Usually, a good test is to use the standard Window video driver to see if this provides the necessary relief.

2) We have found that changing the spooler settings in the printer control panel can often provide relief. "RAW" mode is usually preferable, and usually results in much faster printing. There are exceptions, such as the Lexmark series that can use the PMJournal method of spooling, perform extremely well using this setting. Usually "EMF" spooling is the least desirable.

3) Third part spooling subsystems sometimes are substandard. The Xerox WorkCentre line of printers fit into this category, where they will fail to print under many applications. We suggest you take a look at the following FAQ: Help! My Xerox WorkCentre does not print from Delphi and C++Builder!

We have also found instances where blitting a large amount of data in a short time to the printer will cause a failure. The TExcellent line of printing products provide a function that will cause the application to sleep a bit between Blt calls, allowing the spooler/driver time to digest the image data. Please see the accompanying documentation for use of the PrnDIBSetSleepValue() and PrnFormSetSleepValue() functions.

4) Some print drivers claim to support the StretchDIBits() call, then fail on some bitmaps. The Xerox WorkCentre line of printers fit into this category, where we have found many instances of valid bitmaps that will cause the printer to fail. The work around is to use Device Dependent Bitmaps. The TExcellent line of printing products provide functions that causes images to be transferred using Device Dependent Bitmaps:

PrnFormSetDebugUseDDB(TRUE);

PrnDIBSetDebugUseDDB(TRUE);

Note that using a Device Dependent Bitmap may cause color loss when used with some printer drivers. Please see the accompaning documentation for use of the PrnFormSetDebugUseDDB() and PrnDIBSetDebugUseDDB() functions.

Note that if print driver tells the GDI it does not support the StretchDIBits(), then you may still safely call the StretchDIBits() function, and the GDI will simulate the call using lower level calls to the print driver.

5) Sometimes, a manufacturer simply ships a bad driver. The remedy is usually to get an updated driver. Oftentimes, Microsoft will include a driver on the Windows installation CD that is usually well written. It is worth noting that sometimes drivers designed for other (but similar) printers can be substituted for a given printer driver.

6) Network printers are often installed incorrectly. We have found that many printers do not have official support for networking, however, the manufacturer may give some helpfull advice for networking a given printer. The Lexmark Z11 printer is a good example, in that networking this printer is not officially supported, however Lexmark provides some excellent advice for adding it to the network. In the case of the Lexmark Z11 printer, normal network installation techniques often result in failed or extremely large print jobs. Installing the drivers locally to each machine, then remapping the local port to a network path works very well.

Finally, we recommend that you read the following FAQ:
How can I improve the reliability of printing from my application?

Back to the FAQ


Help! My Xerox WorkCentre does not print from Delphi and C++Builder!

We have investigated this problem, and have found it to be a printer manufacturer issue. None the less, we have found a work-around.

Specifically, you will need to use the patch described below for TPrinter to get your application printing text and non-image based graphics. Once you have the patch in place, setting TExcellentFormPrinter or TExcellentImagePrinter to use Device Dependent Bitmaps using the following function provides relief for printing VCL forms and images:

PrnFormSetDebugUseDDB(TRUE);

PrnDIBSetDebugUseDDB(TRUE);

Additional information on the use of these functions can be found in the online product documentation for the TExcellentImagePrinter and TExcellentFormPrinter products. We also suggest that you take a look at our No Output FAQ.

Patching TPrinter's AbortProc:

The idea here is that we want to patch TPrinter's AbortProc to *optionally* provide a Sleep() statement every time it is called. We want an optional Sleep() statement, as this function can be called *many* times during a print job, and unless you are having problems with a given printer (like the Xerox WorkCentre line of printers), you really do not want to add any delay to your print job.

Normally, to add this functionality, you would simply add a new method to the TPrinter object and rebuild the printers unit, however, according to my friend and former colleague at Borland, Anders Hejlsberg, (the former Chief Architect of Delphi, C++Builder, and the VCL), you should *never* change the interface portion of any of the VCL units, else it can "break" the VCL, as many of the VCL units depend on the printers unit. This opinion was shared by my old buddy Steve Teixeira, as well as Danny Thorpe.

This issue came up when I was writing a chapter for the book "Delphi 3 Special Edition" published by Que. I wanted to add the ability to make changes to the devicemode structure in mid print job. This required adding an optional application callback function to the printers unit that would get called between the end of each page, and the beginning of the next. I consulted the team, and was told it would be impossible to add a function pointer to the interface section of the printers unit, as it would break the VCL.

It was Friday afternoon, and I left for the weekend, stopping by the bank to deposit my well earned Borland paycheck. Standing in line, I came up with a solution. If you patched the implementation section of TPrinter to use a secondary unit, then it would be possible for TPrinter to see any variables declared in the interface section of the secondary unit, and no changes required for the interface section of TPrinter. Further, any variables that were visible in the interface section of the secondary unit would also be visible to any application using the secondary unit!

This implies that you could put a function pointer in the interface section of the secondary unit, and set it to nil in the initialization/finalization section. Code in the TPrinter unit could then test to see if this function pointer was nil, and if it was not, then TPrinter could make a call to the function! Since the application also has access to the function pointer in the secondary unit, the application could set this function pointer to point to a function inside the application!

Viola!!!! We could install a callback in the TPrinter unit without breaking the VCL!

I quickly drove back to the Borland campus (narrowly avoiding the Scotts Valley payday speed trap that commonly sits next to one of the side entrances to the building), and ran the new idea by the team, and got the green light with one small warning... Run Time Packages!!! This work-around will not work with the upcoming run time packages to be added to the next version of the VCL. One other small detail... you have to own the professional or Client Server version of the Delphi/C++Builder product, since the standard verision does not ship with the VCL source code.

So how do you patch the VCL's printers unit to call back to the application? First you will need the secondary unit. Luckly, both C++Builder and Delphi can use Pascal units!

We will call the unit "PrnPatch.pas".

Here is the souce code:

unit PrnPatch;
interface
uses
  Windows;
var WorkProc : procedure(Prn: HDC; Error: Integer);
implementation
initialization
  @WorkProc := nil;
finalization
end.

Now that we have the PrnPatch unit, we can get down to actually patching the printers unit. Luckly, both Delphi and C++Builder use Pascal in the printers.pas source file.

Open up the printers.pas file. If you installed the VCL source code with your compiler, it will be located in the source\vcl directory. If you did not install the VCL source code with your compiler, you will need to locate the printers.pas file on your install CD and copy this file to your hard drive. Do not forget to reset the read-only attribute on this file!

The first patch you will need to make is to add the PrnPatch unit to the uses clause in the implementation section of the printers.pas file:

implementation

uses Consts,
     PrnPatch; {Patch}


Next, you will need to search for the AbortProc function, and make the following change:

function AbortProc(Prn: HDC; Error: Integer): Bool; stdcall;
begin
  Application.ProcessMessages;
 {Patch Start}
  if (@PrnPatch.WorkProc <> nil) then begin
    PrnPatch.WorkProc(Prn, Error);
  end;
 {Patch End}
  Result := not FPrinter.Aborted;
end;

Now comes the tough part. I can't stress this part enough! You have to compile the printers.pas unit and get Delphi/C++Builder to actually use the changed file. For this to work, you will need to turn off the use of runtime packages. To get it to work really well, you should rebuild all the VCL units! With Delphi, this is as easy as adding the source\vcl directory to the *beginning* of the library path, then restart Delphi. The next time you do a "build all", it will rebuild all the VCL units. For C++Builder, it sometimes seems to be a little tougher to get Builder to compile and then actually use the updated files. The best way to verify that everything is working as it should, you should place some breakpoints in the code and make absolutely positive the new code is actually getting called!

For a simple addition, using the "Add to project" option from the IDE's menu works real well. You will want to add both the printer.pas unit and the PrnPatch.pas unit. If you are using C++Builder, it will conveniently create a .hpp file for you to add into your projects unit.

Speaking of the new code, we need to add our callback function to our application and test it out!

Here is the application code for Delphi:

uses Printers,
     PrnPatch;


procedure MyWorkProc(Prn : HDC; Error : integer);
begin
  if (WeWantToSleepForBuggySpoolersAndDrivers) then begin
    Sleep(1000);
  end;
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
  Prnpatch.WorkProc := @MyWorkProc;
  Printer.BeginDoc;
  Printer.Canvas.TextOut(100,100,'TEST');
  Printer.EndDoc;
end;

Here is the application code for C++Builder:

#include "printers.hpp"
#include "PrnPatch.hpp"

void __fastcall MyWorkProc(HDC Prn, int Error)
{
  if (WeWantToSleepForBuggySpoolersAndDrivers) {
    Sleep(1000);
  }
}



void __fastcall TForm1::Button1Click(TObject *Sender)
{
  Prnpatch::WorkProc = MyWorkProc;
  Printer()->BeginDoc();
  Printer()->Canvas->TextOut(100,100,"TEST");
  Printer()->EndDoc();
}

There you have it! Notice that I used a boolean variable called "WeWantToSleepForBuggySpoolersAndDrivers". You will need to add an appropriately named variable in your application to track if you really want to call the sleep function or not. Remember, most drivers do not need this, and it can really slow your print job to a crawl. It would probably be best if your application had a "Printer Configuration" dialog box, where the user could optionally set this value.

Finally, we recommend that you read the following FAQs:
How can I improve the reliability of printing from my application?

Back to the FAQ


TExcellent prints lines across the printout!

Simply register the product!

Back to the FAQ


Can I print other things on the page in addition to my form?

Yes!

Back to the FAQ


How can I print VCL forms without displaying them on the screen?

Temporarily set the left and top properties of the form to Screen.Width and Screen.Height, show the form (off the screen), print it, then hide the form and set the left and top properties back where they were!

Back to the FAQ


Can TExcellentFormPrinter print controls instead of the form?

Yes! TExcellentFormPrinter has a clipping rectangle that is set when calling the print function, and it is possible to set the clipping rectangle to print only a selected control. Additionally it is possible to use TExcellentFormPrinter to print the *entire* area of other controls descending from a TScrollingWinControl! This whole operation can be done easily (and transparently to the user). For example, if you wanted to print the entire contents of a grid, you could use a secondary form to hold a copy of the grid, and show the form off the screen while printing the grid! In the case of a Grid type control, you must get the grid (or a copy of the grid) onto the top left hand corner of the secondary form. Make sure that the grid is displaying the starting cell you wish to print from. You then turn off the scroll bars for the grid control, and make the grid as large as all the cells you wish to print, then simply request TExcellentFormPrinter to print the ScrollBox or other form!

Back to the FAQ


Help! My Woll2Woll TwwDBRichEdit does not print!

The Woll2Woll "TwwDBRichEdit" (and probably other descendants of the "TwwCustomRichEdit") control do not correctly print with TExcellentFormPrinter when used with embedded OLE objects.

Since this is a 3rd party control, support will not be added internally to the next version of TExcellentFormPrinter.

The work-around is to use the OnPaintControlCallbackEvent hook and hook the printing of the "TwwCustomRichEdit" control with the following code:

function TForm1.OnPaintControlCallbackEvent(TheControl : TControl;
                                            dc : HDC) : BOOL;
var
  Range : TFormatRange;
  lpx : integer;
  lpy : integer;
  LastChar : integer;
  MaxLen : integer;
  SaveIndex : integer;
begin
  if (HasClass(TheControl,
               'TwwDBRichEdit')) then begin
   {Save the state of the device context}
    SaveIndex := SaveDc(Dc);
   {Zero out the Range variable}
    FillChar(Range,
             sizeof(Range),
             0);
   {Set the dc members}
    Range.hdc := dc;
    Range.hdcTarget := dc;
   {Get the DPI of the device context}
    lpx := GetDeviceCaps(dc,
                         LOGPIXELSX);
    lpy := GetDeviceCaps(dc,
                         LOGPIXELSY);
   {Lets fix up a rectangle to print to. The rectangle needs to be in TWIPS.}
   {Some folks may perfer to use the Controls ClientRect and offset the rectangle
   {to get a more accurate rendering and account for possibly drawing a border}
    Range.rc.Left := 0;
    Range.rc.Top :=  0;
    Range.rc.Right := TheControl.Width  * 1440 div lpx;
    Range.rc.Bottom := TheControl.Height * 1440 div lpy;
    Range.rcPage := Range.rc;
   {We will loop through and print the RichEdit in bands using the EM_FORMATRANGE message}
    LastChar := 0;
    MaxLen := TRichEdit(TheControl).GetTextLen;
    Range.chrg.cpMax := -1;
    repeat
      Range.chrg.cpMin := LastChar;
      LastChar := SendMessage(TWinControl(TheControl).Handle,
                              EM_FORMATRANGE,
                              1,
                              DWORD(@Range));
    until (LastChar >= MaxLen) or (LastChar = -1);
   {Windows docs say we must do this when done to clear some internal RichEdit buffers!}
    SendMessage(TWinControl(TheControl).Handle,
                EM_FORMATRANGE,
                0,
                0);
   {Restore the state of the device context}
    RestoreDc(Dc,
              SaveIndex);
   {Let TExcellentFormPrinter know we handled the printing of this control!}
    result := TRUE;
   {We are done!}
    exit;
  end;
 {If we got here, then we should let TExcellentFromPrinter Handle printing
the control}
  result := FALSE;
end;

Back to the FAQ


Help! My SpeedButton does not print in the down position!

We have seen this on some video cards in 15, 16, or 32 bit color modes. We handle this internally if the parent contol of the SpeedButton is the same as the control that you are printing. If the parent is another embedded control, the only remedy is to use the following PrintableSpeedButton component, and set the IsPrinting property to true when you are printing:

unit PrintableSpeedButton;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Buttons;

type
  TPrintableSpeedButton = class(TSpeedButton)
  private
    { Private declarations }
    FIsPrinting: Boolean;
  protected
    { Protected declarations }
    procedure Paint; override;
  public
    { Public declarations }
    property IsPrinting: Boolean read FIsPrinting write FIsPrinting;
  published
    { Published declarations }
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TPrintableSpeedButton]);
end;


procedure TPrintableSpeedButton.Paint;
var
  ScreenDc : HDC;
  MemDc : HDC;
  MemBitmap : hBitmap;
  OldMemBitmap : hbitmap;
  OldCanvasHandle : THandle;
  ColorDepth : integer;
begin
  Inherited Paint;
  if (NOT FIsPrinting) then begin
    exit;
  end;
  ScreenDc := GetDc(0);
  ColorDepth := (GetDeviceCaps(ScreenDc,
                               BITSPIXEL) *
                 GetDeviceCaps(ScreenDc,
                               PLANES));
  if ((ColorDepth <> 15) AND
      (ColorDepth <> 16) AND
      (ColorDepth <> 32)) then begin
    ReleaseDc(0,
              ScreenDc);
    exit;
  end;
  //Paint it again using a memory dc for transfer!
  MemDc := CreateCompatibleDc(ScreenDc);
  MemBitmap := CreateCompatibleBitmap(ScreenDc,
                                      Width,
                                      Height);
  ReleaseDc(0,
            ScreenDc);
  OldMemBitmap := SelectObject(MemDc,
                               MemBitmap);
  PatBlt(MemDc,
         0,
         0,
         Width,
         Height,
         WHITENESS);
  OldCanvasHandle := Canvas.Handle;
  Canvas.Handle := MemDc;
  Inherited Paint;
  Canvas.Handle := OldCanvasHandle;
  BitBlt(Canvas.Handle,
         0,
         0,
         Width,
         Height,
         MemDc,
         0,
         0,
         SRCCOPY);
  SelectObject(MemDc,
               OldMemBitmap);
  DeleteObject(MemBitmap);
  DeleteDc(MemDc);
  exit;
end;

end.

Back to the FAQ


Help! My Async Terminal does not print!

The Async Terminal does not correctly print with TExcellentFormPrinter.

Since this is a 3rd party control, support will not be added internally to the next version of TExcellentFormPrinter.

The work-around is to use the OnPaintControlCallbackEvent hook and hook the printing of the "TAdTerminal" control with the following code:

function TForm1.OnPaintControlCallbackEvent(TheControl : TControl;
                                            dc : HDC) : BOOL;
var
  m : TMessage;
  ScreenDc : HDC;
  MemDc : HDC;
  MemBitmap : hBitmap;
  OldMemBitmap : hbitmap;
begin
  if (HasClass(TheControl,
               'TAdTerminal')) then begin
      ScreenDc := GetDc(0);
      MemDc := CreateCompatibleDc(ScreenDc);
      MemBitmap := CreateCompatibleBitmap(ScreenDc,
                                          TheControl.Width,
                                          TheControl.Height);
      ReleaseDc(0,
                ScreenDc);
      OldMemBitmap :=SelectObject(MemDc,
                                  MemBitmap);
      PatBlt(MemDc,
             0,
             0,
             TheControl.Width,
             TheControl.Height,
             WHITENESS);
      m.Msg := WM_ERASEBKGND;
      m.WParam := MemDc;
      m.LParam := 0;
      m.Result := 0;
      TheControl.Dispatch(m);
      m.Msg := WM_PRINT;
      m.WParam := MemDc;
      m.LParam := PRF_NONCLIENT
                  OR PRF_OWNED;
      m.Result := 0;
      TheControl.Dispatch(m);
      m.Msg := WM_PAINT;
      m.WParam := MemDc;
      m.LParam := 0;
      m.Result := 0;
      TheControl.Dispatch(m);
      BitBlt(Dc,
             0,
             0,
             TheControl.Width,
             TheControl.Height,
             MemDc,
             0,
             0,
             SRCCOPY);
      SelectObject(MemDc,
                   OldMemBitmap);
      DeleteObject(MemBitmap);
      DeleteDc(MemDc);
    result := TRUE;
    exit;
  end;
  result := FALSE;
end;

Back to the FAQ


What happened to the debug variables in TExcellentProducts?

Many of our CBuilder users have asked that we make it easier to set these variables, so we have added functions to set the variables with a simple function call. Additional information on the use of these functions can be found in the online product documentation for the TExcellentImagePrinter and TExcellentFormPrinter products.

Back to the FAQ


What happened with the short circuit boolean evaluation problem?

In earlier Delphi versions of the TExcellent product line (version 2.0 and below), turning off short circuit boolean evaluation would cause access violations unless you manually added the {$B-} compiler directive to the units header.

As of version 2.1 of the TExcellent product line, this is no longer necessary.

Back to the FAQ


Help! I want to scale my bitmap to fit...!

We recommend you take a look at the documentation for the PrnUtils unit where there are a number of scaling functions to assist you in scaling any sort of output to any size you need.

We do get a lot of requests for variations of our "Big" PrintDIBitmapEx() example (located in the PrnDib documentation) where we stretch output across multiple pages.

The following code example demonstrates several different stretches. Simply un-comment the stretch type you need, and replace the appropriate code given in the PrintDIBitmapEx() example. While the code is given in Pascal, the changes are very minor to the original code, and it should take very little effort to port the code over to "C".

The possibilities given are:

1) scale to some amount of inches and automatically calculate the number of pages required.

2) scale to some amount of pixels and automatically calculate the number of pages required.

3) scale the image to fit the width of a single page and let the height of the image freeflow, stretching across multiple pages if necessary, with automatic calculations of the number of pages required.

3) scale the form to fit the height of a single page and let the width of the image freeflow, stretching across multiple pages if necessary, with automatic calculations of the number of pages required.

{define a data type we will use during a callback}
{to update an abort dialog with a status update}
type
  PAppCallbackData = ^TAppCallbackData;
  TAppCallbackData = packed record
    AbortDialog : TAbortDialog; {The abort dialog}
    PageAcross : integer;       {The current page across}
    PageDown : integer;         {The current page down}
  end;


{our optional status update callback event}
function AppCallbackFn(UnitsDone : DWORD;
                       TotalUnits : DWORD;
                       UserData : PRNDIB_PTR_AS_UINT) : BOOL; stdcall;
var
  s : string;
  TheData : PAppCallbackData;
begin
 {cast UserData into a useable form}
  TheData := PAppCallbackData(UserData);
 {Update the abort dialogs caption to give a status update}
  s := 'Printing Page ' +
       IntToStr(TheData^.PageAcross) +
       ':' +
       IntToStr(TheData^.PageDown) +
       ' - ' +
       IntToStr(Trunc(((UnitsDone / TotalUnits) * 100))) +
       '%';
  AbortDialogSetCaption(TheData^.AbortDialog,
                        pChar(s));
 {Should we continue?}
  result := (NOT AbortDialogUserHasCanceled(TheData^.AbortDialog));
end;


procedure TForm1.Button1Click(Sender: TObject);
var
  BitmapInfo : PBITMAPINFO;           {pointer to a BitmapInfo structure}
  Bits : pointer;                     {pointer to the bits}
  BitmapWidth : integer;              {bitmap width}
  BitmapHeight : integer;             {bitmap height}
  ReturnValue : integer;              {success or error code}
  PagesWide : integer;                {How many pages wide you want.}
  PagesHigh : integer;                {How many pages high you want.}
  CenterImageOnPage : BOOL;           {Center on the page or print at the top left corner}
  AbortDialog : TAbortDialog;         {The Abort Dialog}
  AppCallbackData : TAppCallbackData; {Our callback data structure}
  PrnPageInfo : TPrnPageInfo;         {Info about the page to be printed}
  TotalImageWidth : integer;          {Total width of the printout (including multiple pages)}
  TotalImageHeight : integer;         {Total height of the printout (including multiple pages)}
  PrintedImageWidth : integer;        {Total printed width of the image (including multiple pages)}
  PrintedImageHeight : integer;       {Total printed height of the umage (including multiple pages)}
  PrintedImageOffset : TPoint;        {How much to offset the image to print it centered on the page(s)}
  i : integer;                        {Loop variable (for i := 1 to PagesHigh do)}
  j : integer;                        {Loop variable (for j := 1 to PagesWide do)}
  SaveIndex : integer;                {Used to save the state of the printer}
  APageCaption : string;              {Used to print a caption on the page and update the abort dialog}
  PageRect : TRect;                   {The clipping rectangle for the page}
  ScaleInfo : TScaleInfo;             {Used to calculate scaling}
begin

 {Center the output on the page? (FALSE prints the image at the top left of the page).}
 {Note that on large printouts - some pages may be blank if you choose to center}
 {the printout, as it will truely center the image, and some pages may not contain}
 {any part of the image}
  CenterImageOnPage := TRUE;

 {Note: we are done with configurable print choices!}

 {Are we printing something right now?}
  if (Printer.Printing) then begin
    ShowMessage('Already printing... Please try again later!');
    exit;
  end;

 {Load a bitmap}
  if (NOT LoadDIBFromFile('test.bmp',
                          pointer(BitmapInfo),
                          Bits,
                          BitmapWidth,
                          BitmapHeight)) then begin
    ShowMessage('Bitmap load error');
    exit;
 end;

 {disable the window so the user cannot click on anything while we are printing.}
  EnableWindow(self.Handle,
               FALSE);

 {try to fire up the printjob!}
  try
    Printer.BeginDoc;
  except
   {error! clean up and bail out!}
    EnableWindow(self.Handle,
                 TRUE);
    ShowMessage('Printer corrupt or not installed!');
    FreeMemEx(BitmapInfo);
    FreeMemEx(Bits);
    exit;
  end;

 {create our abort dialog}
  AbortDialog := CreateAbortDialog(Application.Handle,
                                   self);
  if (AbortDialog = nil) then begin
   {error! clean up and bail out!}
    Printer.Abort;
    EnableWindow(self.Handle,
                 TRUE);
    ShowMessage('Abort dialog could not be created!');
    FreeMemEx(BitmapInfo);
    FreeMemEx(Bits);
    exit;
  end;

 {Set our callback information}
  AppCallbackData.AbortDialog := AbortDialog;

 {get the page information}
  GetPrnPageInfo(Printer.Canvas.Handle,
                 @PrnPageInfo);

 {do some calculations to stretch and position}
 {the image to fit the page (or pages).}
  PageRect.Left := 0;
  PageRect.Top := 0;
  PageRect.Right := PrnPageInfo.AdjustedPageArea.x;
  PageRect.Bottom := PrnPageInfo.AdjustedPageArea.y;


  { You can use the following code if you simply }
  { want to scale to some amount of inches (6 inches is used as an example) }
  { and have the number of pages high and wide calculated for you. }
  { -------------------------------------------------------------- }
  {
    TotalImageWidth := 6 * PrnPageInfo.DPI.x;
    TotalImageHeight := 6 * PrnPageInfo.DPI.y;
    PagesWide := TotalImageWidth div PrnPageInfo.AdjustedPageArea.x;
    if ((TotalImageWidth mod PrnPageInfo.AdjustedPageArea.x) <> 0) then begin
      inc(PagesWide);
    end;
    PagesHigh := TotalImageHeight div PrnPageInfo.AdjustedPageArea.y;
    if ((TotalImageHeight mod PrnPageInfo.AdjustedPageArea.y) <> 0) then begin
      inc(PagesHigh);
    end;
  }


  { You can use the following code if you simply }
  { want to scale to some amount of pixels }
  { and have the number of pages high and wide calculated for you. }
  { -------------------------------------------------------------- }
  {
    TotalImageWidth := Set_this_to_the_number_of_pixels_wide_you_wish;
    TotalImageHeight := Set_this_to_the_number_of_pixels_high_you_wish;
    PagesWide := TotalImageWidth div PrnPageInfo.AdjustedPageArea.x;
    if ((TotalImageWidth mod PrnPageInfo.AdjustedPageArea.x) <> 0) then begin
     inc(PagesWide);
    end;
    PagesHigh := TotalImageHeight div PrnPageInfo.AdjustedPageArea.y;
    if ((TotalImageHeight mod PrnPageInfo.AdjustedPageArea.y) <> 0) then begin
     inc(PagesHigh);
    end;
  }


  { You can use the following code if you simply }
  { want to scale the image width to fit a single }
  { page, and let the height freeflow to the number }
  { of pages needed : }
  { -------------------------------------------------------------- }
  {
   ScaleInfo.OriginalSize_X := BitmapWidth;
   ScaleInfo.OriginalSize_Y := BitmapHeight;
   ScaleInfo.ScaledSize_X := PrnPageInfo.AdjustedPageArea.x;
   ScaleToFitX(@ScaleInfo);
   PagesWide := 1;
   TotalImageWidth := Trunc(ScaleInfo.ScaledSize_X);
   TotalImageHeight := Trunc(ScaleInfo.ScaledSize_Y);
   PagesHigh := TotalImageHeight div PrnPageInfo.AdjustedPageArea.y;
   if ((TotalImageHeight mod PrnPageInfo.AdjustedPageArea.y) <> 0) then begin
     inc(PagesHigh);
   end;
   }


  { You can use the following code if you simply }
  { want to scale the image height to fit a single }
  { page, and let the width freeflow to the number }
  { of pages needed : }
  {
   ScaleInfo.OriginalSize_X := BitmapWidth;
   ScaleInfo.OriginalSize_Y := BitmapHeight;
   ScaleInfo.ScaledSize_Y := PrnPageInfo.AdjustedPageArea.y;
   ScaleToFitY(@ScaleInfo);
   PagesHigh := 1;
   TotalImageWidth := Trunc(ScaleInfo.ScaledSize_X);
   TotalImageHeight := Trunc(ScaleInfo.ScaledSize_Y);
   PagesWide := TotalImageWidth div PrnPageInfo.AdjustedPageArea.x;
   if ((TotalImageWidth mod PrnPageInfo.AdjustedPageArea.x) <> 0) then begin
     inc(PagesWide);
   end;
   }

  PrintedImageWidth := TotalImageWidth;
  PrintedImageHeight := TotalImageHeight;

  if (NOT CenterImageOnPage) then begin
    PrintedImageOffset.x := 0;
    PrintedImageOffset.y := 0;
  end else begin
    PrintedImageOffset.x := ((PrnPageInfo.AdjustedPageArea.x * PagesWide) div 2) - (TotalImageWidth div 2);
    PrintedImageOffset.y := ((PrnPageInfo.AdjustedPageArea.y * PagesHigh) div 2) - (TotalImageHeight div 2);
  end;


 {loop through and print the pages!}
  for i := 1 to PagesHigh do begin
    for j := 1 to PagesWide do begin

     {set our callback information}
      AppCallbackData.PageAcross := j;
      AppCallbackData.PageDown := i;

     {construct a caption for the abort dialog (we will}
     {also print this at the top of each printed page).}
      APageCaption := 'Printing Page ' +
                      IntToStr(j) +
                      ':' +
                      IntToStr(i) +
                      ' - 0%';

     {set the abort dialog's caption to give the user a status update}
      AbortDialogSetCaption(AbortDialog,
                            pChar(APageCaption));

     {test to see if the user canceled the print job}
      if (AbortDialogUserHasCanceled(AbortDialog)) then begin
       {user canceled the print job - clean up and bail out!}
        Printer.Abort;
        FreeAbortDialog(AbortDialog);
        EnableWindow(self.Handle,
                     TRUE);
        FreeMemEx(BitmapInfo);
        FreeMemEx(Bits);
        ShowMessage('Printing Aborted!');
        exit;
      end;

     {save the printer state}
      SaveIndex := SaveDc(Printer.Canvas.Handle);

     {move our origin to account for a perfect printing margin}
      MoveWindowOrg(Printer.Canvas.Handle,
                    PrnPageInfo.AdjustedMarginOffset.x,
                    PrnPageInfo.AdjustedMarginOffset.y);

     {allow drawing only within our prefect margins}
      IntersectClipRect(Printer.Canvas.Handle,
                        0,
                        0,
                        PrnPageInfo.AdjustedPageArea.x,
                        PrnPageInfo.AdjustedPageArea.y);

     {Print the image! We will need to "back up" the offset of where we print}
     {the image to account for printing across multiple pages. No need to fear,}
     {TExcellentImagePrinter will allow us to print up to 2 billion pixels high}
     {and wide, even under Windows 95 and 98!}
     ReturnValue :=
       PrintDIBitmapEx(Printer.Canvas.Handle,
                       -((j - 1) * PrnPageInfo.AdjustedPageArea.x) + PrintedImageOffset.x,
                       -((i - 1) * PrnPageInfo.AdjustedPageArea.y) + PrintedImageOffset.y,
                       PrintedImageWidth,
                       PrintedImageHeight,
                       0,
                       0,
                       BitmapInfo^.bmiHeader.biWidth,
                       BitmapInfo^.bmiHeader.biHeight,
                       BitmapInfo,
                       Bits,
                       0,
                       TRUE,
                       FALSE,
                       PageRect,
                       @AppCallbackFn,
                       PRNDIB_PTR_AS_UINT(@AppCallbackData));
       if (ReturnValue < NOTHING_TO_PRINT) then begin
       {This will happen if the user has canceled the print job}
       {or an error occured. We will not trap the NOTHING_TO_PRINT}
       {error, as when the image is stretched across several pages, it}
       {is possible that a few pages many not contain part of the image}
       {due to page and image centering, and TExcellentImage printer will}
       {report there is nothing to print for those pages. If we have another}
       {kind of error then we will clean up and bail out!}
        RestoreDc(Printer.Canvas.Handle,
                  SaveIndex);
        FreeAbortDialog(AbortDialog);
        Printer.Abort;
        FreeMemEx(BitmapInfo);
        FreeMemEx(Bits);
        EnableWindow(self.Handle,
                     TRUE);
        case (ReturnValue) of
          BAD_PARAMETER : begin
            ShowMessage('BAD_PARAMETER');
          end;
          MEMORY_ALLOC_FAILED : begin
            ShowMessage('MEMORY_ALLOC_FAILED');
          end;
          MEMORY_READ_FAILED : begin
            ShowMessage('MEMORY_READ_FAILED');
          end;
          MEMORY_WRITE_FAILED : begin
            ShowMessage('MEMORY_WRITE_FAILED');
          end;
          USER_ABORT_OR_OTHER_ERROR : begin
            ShowMessage('USER_ABORT_OR_OTHER_ERROR!');
          end;
        end;
        exit;
      end;

     {we can print some lovely page captions here (or anything else we care to print)!}
      Printer.Canvas.Font.Name := 'Arial';
     {fix a Delphi issue that pops up sometimes with font scaling!}
      Printer.Canvas.Font.PixelsPerInch := PrnPageInfo.DPI.y;
      Printer.Canvas.Font.Size := 10;
     {create a page caption to print}
      APageCaption := 'Printing Page ' +
                      IntToStr(j) +
                      ' : ' +
                      IntToStr(i);
      Printer.Canvas.TextOut(0,
                             0,
                             APageCaption);

     {we are done printing on this page, restore the printer state!}
      RestoreDc(Printer.Canvas.Handle,
                SaveIndex);

     {if not the last page, we need to call NewPage!}
      if (NOT ((j = PagesWide) AND
               (i = PagesHigh))) then begin
        Printer.NewPage;
      end;

    end; {for j := 1 to PagesWide}
  end; {for i := 1 to PagesHigh}

 {We are done! Time to clean up!}
  FreeMemEx(BitmapInfo);
  FreeMemEx(Bits);
  FreeAbortDialog(AbortDialog);
  Printer.EndDoc;
  EnableWindow(self.Handle,
               TRUE);
  ShowMessage('Printing Completed!');
end;

Back to the FAQ



Help! I want to scale my form to fit...!

We recommend you take a look at the documentation for the PrnUtils unit where there are a number of scaling functions to assist you in scaling any sort of output to any size you need.

We do get a lot of requests for variations of our "Big" PrintTScrollingWinControlEx() example (located in the PrnForm documentation) where we stretch output across multiple pages.

The following code example demonstrates several different stretches. Simply un-comment the stretch type you need, and replace the appropriate code given in the PrintTScrollingWinControlEx() example. While the code is given in Pascal, the changes are very minor to the original code, and it should take very little effort to port the code over to "C".

The possibilities given are:

1) scale to some amount of inches and automatically calculate the number of pages required.

2) scale to some amount of pixels and automatically calculate the number of pages required.

3) scale the form to fit the width of a single page and let the height of the form freeflow, stretching across multiple pages if necessary, with automatic calculations of the number of pages required.

4) scale the form to fit the height of a single page and let the width of the form freeflow, stretching across multiple pages if necessary, with automatic calculations of the number of pages required.

{define a data type we will use during a callback}
{to update an abort dialog with a status update}
type
  PAppCallbackData = ^TAppCallbackData;
  TAppCallbackData = packed record
    AbortDialog : TAbortDialog; {The abort dialog}
    PageAcross : integer;       {The current page across}
    PageDown : integer;         {The current page down}
  end;


{our optional status update callback event}
function AppCallbackFn(UnitsDone : DWORD;
                       TotalUnits : DWORD;
                       UserData : PRNFORM_PTR_AS_UINT) : BOOL; stdcall;
var
  s : string;
  TheData : PAppCallbackData;
begin
 {cast UserData into a useable form}
  TheData := PAppCallbackData(UserData);
 {Update the abort dialogs caption to give a status update}
  s := 'Printing Page ' +
       IntToStr(TheData^.PageAcross) +
       ':' +
       IntToStr(TheData^.PageDown) +
       ' - ' +
       IntToStr(Trunc(((UnitsDone / TotalUnits) * 100))) +
       '%';
  AbortDialogSetCaption(TheData^.AbortDialog,
                        pChar(s));
 {Should we continue?}
  result := (NOT AbortDialogUserHasCanceled(TheData^.AbortDialog));
end;


procedure TForm1.Button1Click(Sender: TObject);
var
  PagesWide : integer;                {How many pages wide you want.}
  PagesHigh : integer;                {How many pages high you want.}
  CenterFormOnPage : BOOL;            {Center on the page or print at the top left corner}
  AbortDialog : TAbortDialog;         {The Abort Dialog}
  AppCallbackData : TAppCallbackData; {Our callback data structure}
  PrnPageInfo : TPrnPageInfo;         {Info about the page to be printed}
  VirtualFormSize : TSize;            {Size of the form (including non visible scrolled areas}
  TotalImageWidth : integer;          {Total width of the printout (including multiple pages)}
  TotalImageHeight : integer;         {Total height of the printout (including multiple pages)}
  PrintedImageOffset : TPoint;        {How much to offset the form to print it centered on the page(s)}
  i : integer;                        {Loop variable (for i := 1 to PagesHigh do)}
  j : integer;                        {Loop variable (for j := 1 to PagesWide do)}
  SaveIndex : integer;                {Used to save the state of the printer}
  AnAbortCaption : string;            {Used to update the abort dialog}
  PageRect : TRect;                   {The clipping rectangle for the page}
  PrintFormData : TPrintFormData;     {Additional parameters}
  ScaleInfo : TScaleInfo;             {Used to calculate scaling}
begin
 {center the output on the page? (FALSE prints the form at the top left of the page).}
  CenterFormOnPage := TRUE;

  FillChar(PrintFormData,
           sizeof(PrintFormData),
           0);
  PrintFormData.StrucSize := sizeof(PrintFormData);

 {Note: We are done with configurable print choices!}

 {disable the window so the user cannot click on anything while we are printing.}
  EnableWindow(self.Handle,
               FALSE);

 {try to fire up the printjob!}
  try
    Printer.BeginDoc;
  except
   {error! clean up and bail out!}
    EnableWindow(self.Handle,
                 TRUE);
    ShowMessage('Printer corrupt or not installed!');
    exit;
  end;

 {create our abort dialog}
  AbortDialog := CreateAbortDialog(Application.Handle,
                                   self);

 {Set our static callback information}
  AppCallbackData.AbortDialog := AbortDialog;

 {get the page information}
  GetPrnPageInfo(Printer.Handle,
                 @PrnPageInfo);

  VirtualFormSize := VirtualClientSize(self);

 {do some calculations to stretch and position}
 {the image to fit the page (or pages).}
  PageRect.Left := 0;
  PageRect.Top := 0;
  PageRect.Right := PrnPageInfo.AdjustedPageArea.x;
  PageRect.Bottom := PrnPageInfo.AdjustedPageArea.y;


  { You can use the following code if you simply }
  { want to scale to some amount of inches (6 inches is used as an example) }
  { and have the number of pages high and wide calculated for you. }
  { -------------------------------------------------------------- }
  {
    TotalImageWidth := 16 * PrnPageInfo.DPI.x;
    TotalImageHeight := 16 * PrnPageInfo.DPI.y;
    PagesWide := TotalImageWidth div PrnPageInfo.AdjustedPageArea.x;
    if ((TotalImageWidth mod PrnPageInfo.AdjustedPageArea.x) <> 0) then begin
      inc(PagesWide);
    end;
    PagesHigh := TotalImageHeight div PrnPageInfo.AdjustedPageArea.y;
    if ((TotalImageHeight mod PrnPageInfo.AdjustedPageArea.y) <> 0) then begin
      inc(PagesHigh);
    end;
   }


  { You can use the following code if you simply }
  { want to scale to some amount of pixels }
  { and have the number of pages high and wide calculated for you. }
  { -------------------------------------------------------------- }
   {
    TotalImageWidth := Set_this_to_the_number_of_pixels_wide_you_wish;
    TotalImageHeight := Set_this_to_the_number_of_pixels_high_you_wish;
    PagesWide := TotalImageWidth div PrnPageInfo.AdjustedPageArea.x;
    if ((TotalImageWidth mod PrnPageInfo.AdjustedPageArea.x) <> 0) then begin
     inc(PagesWide);
    end;
    PagesHigh := TotalImageHeight div PrnPageInfo.AdjustedPageArea.y;
    if ((TotalImageHeight mod PrnPageInfo.AdjustedPageArea.y) <> 0) then begin
     inc(PagesHigh);
    end;
    }


  { You can use the following code if you simply }
  { want to scale the form width to fit a single }
  { page, and let the height freeflow to the number }
  { of pages needed : }
  { -------------------------------------------------------------- }
  {
   ScaleInfo.OriginalSize_X := VirtualFormSize.cx;
   ScaleInfo.OriginalSize_Y := VirtualFormSize.cy;
   ScaleInfo.ScaledSize_X := PrnPageInfo.AdjustedPageArea.x;
   ScaleToFitX(@ScaleInfo);
   PagesWide := 1;
   TotalImageWidth := Trunc(ScaleInfo.ScaledSize_X);
   TotalImageHeight := Trunc(ScaleInfo.ScaledSize_Y);
   PagesHigh := TotalImageHeight div PrnPageInfo.AdjustedPageArea.y;
   if ((TotalImageHeight mod PrnPageInfo.AdjustedPageArea.y) <> 0) then begin
     inc(PagesHigh);
   end;
  }


  { You can use the following code if you simply }
  { want to scale the form height to fit a single }
  { page, and let the width freeflow to the number }
  { of pages needed : }
  {
   ScaleInfo.OriginalSize_X := VirtualFormSize.cx;
   ScaleInfo.OriginalSize_Y := VirtualFormSize.cy;
   ScaleInfo.ScaledSize_Y := PrnPageInfo.AdjustedPageArea.y;
   ScaleToFitY(@ScaleInfo);
   PagesHigh := 1;
   TotalImageWidth := Trunc(ScaleInfo.ScaledSize_X);
   TotalImageHeight := Trunc(ScaleInfo.ScaledSize_Y);
   PagesWide := TotalImageWidth div PrnPageInfo.AdjustedPageArea.x;
   if ((TotalImageWidth mod PrnPageInfo.AdjustedPageArea.x) <> 0) then begin
     inc(PagesWide);
   end;
   }


  if (NOT CenterFormOnPage) then begin
    PrintedImageOffset.x := 0;
    PrintedImageOffset.y := 0;
  end else begin
    PrintedImageOffset.x := ((PrnPageInfo.AdjustedPageArea.x * PagesWide) div 2) - (TotalImageWidth div 2);
    PrintedImageOffset.y := ((PrnPageInfo.AdjustedPageArea.y * PagesHigh) div 2) - (TotalImageHeight div 2);
  end;


 {loop through and print the pages!}
  for i := 1 to PagesHigh do begin
    for j := 1 to PagesWide do begin

     {set our dynamic callback information}
      AppCallbackData.PageAcross := j;
      AppCallbackData.PageDown := i;


     {set the abort dialog's caption to give the user a status update}
      AnAbortCaption := 'Printing Page ' +
                        IntToStr(j) +
                        ':' +
                        IntToStr(i) +
                        ' - 0%';
      AbortDialogSetCaption(AbortDialog,
                            pChar(AnAbortCaption));

     {test to see if the user canceled the print job}
      if (AbortDialogUserHasCanceled(AbortDialog)) then begin
       {user canceled the print job - clean up and bail out!}
        Printer.Abort;
        FreeAbortDialog(AbortDialog);
        EnableWindow(self.Handle,
                     TRUE);
        ShowMessage('Printing Aborted!');
        exit;
      end;

     {save the printer state}
      SaveIndex := SaveDc(Printer.Canvas.Handle);

     {move our origin to account for a perfect printing margin}
      MoveWindowOrg(Printer.Canvas.Handle,
                    PrnPageInfo.AdjustedMarginOffset.x,
                    PrnPageInfo.AdjustedMarginOffset.y);

     {allow drawing only within our prefect margins}
      IntersectClipRect(Printer.Canvas.Handle,
                        0,
                        0,
                        PrnPageInfo.AdjustedPageArea.x,
                        PrnPageInfo.AdjustedPageArea.y);

     {Print the form! We will need to "back up" the offset of where we print}
     {the form to account for printing across multiple pages. No need to fear,}
     {TExcellentPrinter will allow us to print up to 2 billion pixels high}
     {and wide, even under Windows 95 and 98!}
      if (NOT PrintTScrollingWinControlEx(self,
                                          Printer.Canvas.Handle,
                                          -((j - 1) * PrnPageInfo.AdjustedPageArea.x) + PrintedImageOffset.x,
                                          -((i - 1) * PrnPageInfo.AdjustedPageArea.y) + PrintedImageOffset.y,
                                          TotalImageWidth,
                                          TOtalImageHeight,
                                          PageRect,
                                          AppCallbackFn,
                                          PRNFORM_PTR_AS_UINT(@AppCallbackData),
                                          nil, // Optional OnPaintCallbackEvent,
                                          nil, // Optional OnPaintControlCallbackEvent,
                                          @PrintFormData)) then begin
       {this will happen if the user has canceled the print job}
       {or an error occured. Clean up and bail out!}
        RestoreDc(Printer.Canvas.Handle,
                  SaveIndex);
        FreeAbortDialog(AbortDialog);
        Printer.Abort;
        EnableWindow(self.Handle,
                     TRUE);
        ShowMessage('Printing Aborted!');
        exit;
      end;

     {we can print a lovely page caption here!}
      Printer.Canvas.Font.Name := 'Arial';
     {fix a VCL issue that pops up sometimes with font scaling!}
      Printer.Canvas.Font.PixelsPerInch := PrnPageInfo.DPI.y;
      Printer.Canvas.Font.Size := 10;
      Printer.Canvas.TextOut(0,
                             0,
                             'Printing Page ' +
                             IntToStr(j) +
                             ':' +
                             IntToStr(i));

     {we are done printing on this page, restore the printer state!}
      RestoreDc(Printer.Canvas.Handle,
                SaveIndex);

     {if not the last page, we need to call NewPage!}
      if (NOT ((j = PagesWide) AND
               (i = PagesHigh))) then begin
        Printer.NewPage;
      end;
    end;
  end;

 {We are done! Time to clean up!}
  FreeAbortDialog(AbortDialog);
  Printer.EndDoc;
  EnableWindow(self.Handle,
               TRUE);
  ShowMessage('Printing Completed!');
end;

Back to the FAQ


Validate HTML