Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

TPrinter

521 views
Skip to first unread message

The Software Tailor Ltd.

unread,
Mar 12, 1998, 3:00:00 AM3/12/98
to

I do not want the printer to issue a page eject
after I stop printing. How can I do this ?

I have tried TPrinter with BeginDoc and EndDoc,
but EndDoc trigers a page eject.

I have also tried AssignPrn(SomePrn) etc but
CloseFile(SomePrn) also trigers a page eject.

Does anyone know how to stop the page eject ?

John.

Kurt Barthelmess (TeamB)

unread,
Mar 13, 1998, 3:00:00 AM3/13/98
to

John -

You could write your own printer driver. But as a practical matter,
that's the way drivers operate. You can't suppress the page eject.

If this is a dedicated device, you can treat it as a TextFile, forget
about TPrinter and do all your own control. But, of course, you lose
the Windows fonts and such, not to mention buffering.

Good luck.

Kurt

Hal

unread,
Mar 18, 1998, 3:00:00 AM3/18/98
to

Is it possible then to set the page height? I am having a similiar problem
trying to write an in-house label printing routine. It prints the label but
form feeds for an 11" paper. I try to use the 'custom' size but get an
'invalid printer' error message when I try to print.

If you use just a straight TextFile how do you send it to the printer?

Hal


Kurt Barthelmess (TeamB) <71333...@compuserve.com> wrote in message
<35088bf7...@forums.borland.com>...

Joe C. Hecht (Borland)

unread,
Mar 18, 1998, 3:00:00 AM3/18/98
to

Hal wrote:
>
> Is it possible then to set the page height? I am having a similiar problem
> trying to write an in-house label printing routine. It prints the label but
> form feeds for an 11" paper. I try to use the 'custom' size but get an
> 'invalid printer' error message when I try to print.
>
> If you use just a straight TextFile how do you send it to the printer?

A few ideas....

Q) How can I change the papersize of my print job?

A) One way to change printer settings at the start
of a print job is to change the printer's devicemode
structure.

See: TDEVMODE in the Delphi 1.02 help file or DEVMODE
in the Delphi 2.01 help file for other settings you can
change (providing the print driver supports the change).

The following example, contains code to change the papersize and
the bin that is used:


procedure TForm1.Button1Click(Sender: TObject);
var
Device : array[0..255] of char;
Driver : array[0..255] of char;
Port : array[0..255] of char;
hDMode : THandle;
PDMode : PDEVMODE;
begin
Printer.PrinterIndex := Printer.PrinterIndex;
Printer.GetPrinter(Device, Driver, Port, hDMode);
if hDMode <> 0 then begin
pDMode := GlobalLock(hDMode);
if pDMode <> nil then begin

{Set to legal}
pDMode^.dmFields := pDMode^.dmFields or dm_PaperSize;
pDMode^.dmPaperSize := DMPAPER_LEGAL;

{Set to custom size}
pDMode^.dmFields := pDMode^.dmFields or
DM_PAPERSIZE or
DM_PAPERWIDTH or
DM_PAPERLENGTH;
pDMode^.dmPaperSize := DMPAPER_USER;
pDMode^.dmPaperWidth := 100 {SomeValueInTenthsOfAMillimeter};
pDMode^.dmPaperLength := 100 {SomeValueInTenthsOfAMillimeter};

{Set the bin to use}
pDMode^.dmFields := pDMode^.dmFields or DMBIN_MANUAL;
pDMode^.dmDefaultSource := DMBIN_MANUAL;

GlobalUnlock(hDMode);
end;
end;
Printer.PrinterIndex := Printer.PrinterIndex;
Printer.BeginDoc;
Printer.Canvas.TextOut(100,100, 'Test 1');
Printer.EndDoc;
end;


Q) How do I write a raw string of a data to the printer?

A) Under Win16, you can use the SpoolFile function, or the Passthrough
escape if the printer
supports it. This is detailed in TI3196 - Direct Commands to Printer -
Passthrough/Escape
available from the Borland web site. Under Win32, you should use
WritePrinter. The following is
an example of opening a printer and writing a raw string of data to the
printer. Note that you must
send the correct printer name, such as "HP LaserJet 5MP" for the
function to succeed. You are
also responsible for embedding any necessary control codes that the
printer may require.

uses WinSpool;

procedure WriteRawStringToPrinter(PrinterName: String; S: String);
var
Handle: THandle;
N: DWORD;
DocInfo1: TDocInfo1;
begin
if not OpenPrinter(PChar(PrinterName), Handle, nil) then
begin
ShowMessage('error ' + IntToStr(GetLastError));
Exit;
end;
with DocInfo1 do begin
pDocName := PChar('test doc');
pOutputFile := nil;
pDataType := 'RAW';

end;
StartDocPrinter(Handle, 1, @DocInfo1);
StartPagePrinter(Handle);
WritePrinter(Handle, PChar(S), Length(S), N);
EndPagePrinter(Handle);
EndDocPrinter(Handle);
ClosePrinter(Handle);
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
WriteRawStringToPrinter('HP', 'Test This');
end;


Q) How can I change the printer setup between printed pages?

A) This is normally accomplished by retrieving a copy of the device
mode structure that is associated with the current printer, making
the necessary changes to the device independent portion of the
structure, and then calling the Windows API function ResetDc()
in between pages.

Since the ResetDc() function is disabled by Windows during the
printing of a page, and the TPrinter unit does not have an event
that is fired between pages, a work around is required.

While it is possible to insert and modify code directly in the
implementation section of TPrinter, it is not possible to make
changes to the interface portion of TPrinter and still maintain
stability in the VCL and other third party components. With
the advent of packages, rebuilding the main VCL package is
also not an ideal solution. Finally, TPrinter is one of the few
VCL classes that does not lend itself to subclassing, making it
difficult to add an event that would fire between pages.

Before the advent of packages, the work around consisted of
creating a additional unit that sat between TPrinter and the
unit(s) containing your printing code. A uses statement was
added to the TPrinter unit in the implementation section
that pointed to the new unit (avoiding any change to
TPrinter's interface section). Finally, code was added to
the TPrinter unit that would call a function pointer in the
new unit between each printed page. You could then hook into
TPrinter by adding the new unit to the uses clause of the unit(s)
you are printing from, and reassigning the function pointer to
point to a function in your unit. While this may sound complex,
in practice, it was a clean solution to an interesting problem,
that worked well with both VCL components and custom printing
code produced directly in your application.

With the advent of packages, a new solution is needed, since
the TPrinter unit is compiled into the main VCL system package,
where rebuilding is not recommended. Here we will provide such a
solution in the form of a single unit that encapsulates both
the work around and the printing code. The code is designed
so it will compile under all versions of Delphi and Borland's
C++ Builder. Since there will be no hooks added to the TPrinter
unit, this solution will only work with custom printing code
provided from your application, and not work with VCL and other
"non-aware" third part components that offer a print method, or
code utilizing the AssignPrn() function.

The solution presented allows changes to the page setup by making
direct calls to the Windows API to simulate TPrinter's NewPage
method, and calling the Windows API function ResetDc() between
pages. Note that not all printers support the ResetDc() escape.
Under 16 bit versions of Windows, testing for escape support by
calling Escape(QueryEscapeSupport...) returns the correct value
for the ResetDc undocumented escape code #128, but Windows 95
instead steals the call, and returns true even if the driver does not
support it. The only way to test the if the ResetDc() function
is supported is to make a call directly to the function itself.
To avoid printing blank pages in the event of an error, the
example calls the ResetDC function and passes Printer.Handle
as the DC before the printing actually starts to test for support
of the function. This is ok, as the call is transformed by Windows
to an escape to the print driver, and the dc is not actually passed
but is instead used by Windows to identify the driver. If the print
driver does not support the ResetDc() escape, we will accomplish
changing the printer's settings by printing each page as separate
print job. Since we are bypassing TPrinter's NewPage method, and
since the print job may be accomplished by printing separate pages,
the PageNumber property of TPrinter will not remain accurate though
out the print job and should be tracked by the application. Finally,
if abort method of TPrinter must be called, it should be called in
a section of code after only after a StartPage command.

Note: See TDEVMODE in the Delphi 1.02 help file or DEVMODE
in the Delphi 2.01 help file for other settings you can change, such
as the bin, paper sizes and so forth, providing that the print driver
supports such a change.


Example:

unit Unit1;

interface

{$IFDEF WIN32}
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Printers;
{$ELSE}
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls, Printers, Print;
{$ENDIF}


type
TForm1 = class(TForm)
PrintDialog1: TPrintDialog;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
function PageSetup : bool;
function NewPage : bool;
public
{ Public declarations }
PageNumber : integer;
CanReset : bool;
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

{$IFNDEF WIN32}
const MAX_PATH = 144;
{$ENDIF}

function TForm1.PageSetup : bool;
var
pDevice : pChar;
pDriver : pChar;
pPort : pChar;
hDMode : THandle;
PDMode : PDEVMODE;
PrnHandle : THandle;
begin
result := false;
GetMem(pDevice, cchDeviceName);
GetMem(pDriver, MAX_PATH);
GetMem(pPort, MAX_PATH);
Printer.GetPrinter(pDevice, pDriver, pPort, hDMode);
if hDMode <> 0 then begin
pDMode := GlobalLock(hDMode);
if pDMode <> nil then begin

{Change your printing settings here}
if not odd(PageNumber) then begin
pDMode^.dmFields := pDMode^.dmFields or dm_PaperSize;
pDMode^.dmPaperSize := DMPAPER_LETTER;
end else begin
pDMode^.dmFields := pDMode^.dmFields or dm_PaperSize;
pDMode^.dmPaperSize := DMPAPER_LEGAL;
end;

if Printer.Printing then
PrnHandle := Printer.Canvas.Handle
else
PrnHandle := Printer.Handle;
{$IFDEF WIN32}
if ResetDc(PrnHandle, pDMode^) <> 0 then
{$ELSE}
if ResetDc(PrnHandle, pDMode) <> 0 then
{$ENDIF}
CanReset := true else
CanReset := false;
Result := true;
GlobalUnlock(hDMode);
end;
end;
FreeMem(pDevice, cchDeviceName);
FreeMem(pDriver, MAX_PATH);
FreeMem(pPort, MAX_PATH);
end;

function TForm1.NewPage : bool;
begin
Result := true;
if CanReset then
EndPage(Printer.Canvas.Handle) else
Printer.EndDoc;
Inc(PageNumber);
if PageSetUp then begin
if CanReset then begin
StartPage(Printer.Canvas.Handle);
Printer.Canvas.Refresh;
end else
Printer.BeginDoc;
end else begin
if CanReset then begin
StartPage(Printer.Canvas.Handle);
Printer.Abort;
end;
Result := false;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
if PrintDialog1.Execute then begin

{Setup printing}
PageNumber := 1;
if not PageSetUp then begin
ShowMessage('Unable to customize printer settings');
exit;
end;
Printer.BeginDoc;

{Print page one}
Printer.Canvas.TextOut(100,100, 'Test Page 1');
if not NewPage then begin
ShowMessage('Unable to customize printer settings');
exit;
end;

{Print page two}
Printer.Canvas.TextOut(100,100, 'Test Page 2');
if not NewPage then begin
ShowMessage('Unable to customize printer settings');
exit;
end;

{Print page three}
Printer.Canvas.TextOut(100,100, 'Test Page 3');
Printer.EndDoc;
end;

end;

end.


Q) How can I print in Delphi without using the TPrinter Unit?

A) The following example uses the Windows API function PrintDlg() to
allow the user to choose a printer to use, and utilizes Windows API
functions
to print using the returned dc.

Example:

uses CommDlg;

{$IFNDEF WIN32}
const MAX_PATH = 144;
{$ENDIF}

procedure TForm1.Button1Click(Sender: TObject);
var
Pd : TPrintDlg;
DocInfo: TDocInfo;
begin
FillChar(Pd, sizeof(Pd), #0);
Pd.lStructSize := sizeof(Pd);
Pd.hWndOwner := Form1.Handle;
Pd.Flags := PD_RETURNDC;
if PrintDlg(pd) then begin
FillChar(DocInfo, sizeof(DocInfo), #0);
DocInfo.cbSize := SizeOf(DocInfo);
GetMem(DocInfo.lpszDocName, 32);
GetMem(DocInfo.lpszOutput, MAX_PATH);
lStrCpy(DocInfo.lpszDocName, 'My Document');
{Add this line to print to a file }
lStrCpy(DocInfo.lpszOutput, 'C:\Download\Test.doc');
StartDoc(Pd.hDc, DocInfo);
StartPage(Pd.hDc);
TextOut(Pd.hDc, 100, 100, 'Page 1', 6);
EndPage(Pd.hDc);
StartPage(Pd.hDc);
TextOut(Pd.hDc, 100, 100, 'Page 2', 6);
EndPage(Pd.hDc);
EndDoc(Pd.hDc);
FreeMem(DocInfo.lpszDocName, 32);
FreeMem(DocInfo.lpszOutput, MAX_PATH);
end;
end;


Q) When I call TPrinter's NewPage() method when using the AssignPrn,
the page is ejected, but the text starts printing at the same location
it was
using on the previous page. How can I cause a page break when using
the TPrinters AssignPrn function and get the text to start out at the
top of the
page?

A) Send a FormFeed character char(#12) using the Write command.

Example:

uses Printers;

procedure TForm1.Button1Click(Sender: TObject);
var
MyFile: TextFile;
begin
AssignPrn(MyFile);
Rewrite(MyFile);
Printer.Canvas.Font.Name := 'Courier New';
Printer.Canvas.Font.Style := [fsBold];
Printer.Canvas.Font.PixelsPerInch:=
GetDeviceCaps(Printer.Canvas.Handle, LOGPIXELSY);
Writeln(MyFile, 'Print this text - Page1');
Write(MyFile, #12); {form feed}
Writeln(MyFile, 'Print this text - Page2');
System.CloseFile(MyFile);
end;

Joe
--
Joe C. Hecht
Senior Engineer & Online WizOp
Delphi Developer Support Group
Borland International, Inc.

Hal

unread,
Mar 18, 1998, 3:00:00 AM3/18/98
to

Joe,

Thanks a ton! Have a few questions/problems though.

With the raw output method I got it to work perfectly so for what I need it
was great. With the other example about changing the paperlength I made the
following changes in your code:

pDMode^.dmPaperWidth := 700 {SomeValueInTenthsOfAMillimeter};
pDMode^.dmPaperLength := 300 {SomeValueInTenthsOfAMillimeter};

// Printer.PrinterIndex := -1;// Printer.PrinterIndex; commented this out
and used the code below:

for i:=0 to printer.printers.count-1 do
if pos('kx-p1180',lowercase(printer.printers[i]))>0 then
begin
Printer.PrinterIndex:=i;
break;
end;

I also commented out the code about the bin and the legal paper. When I run
it I get the error 'Printer selected is not valid'. Seems I get this
whenever I try to use custom paper styles. Any ideas?

Hal

Joe C. Hecht (Borland)

unread,
Mar 19, 1998, 3:00:00 AM3/19/98
to Hal

>
> pDMode^.dmPaperWidth := 700 {SomeValueInTenthsOfAMillimeter};
> pDMode^.dmPaperLength := 300 {SomeValueInTenthsOfAMillimeter};
>
> // Printer.PrinterIndex := -1;// Printer.PrinterIndex; commented
I think my code used:

Printer.PrinterIndex := Printer.PrinterIndex;
{causes a reset}

> I also commented out the code about the bin and the legal paper. When

That was only for an example.

I run
> it I get the error 'Printer selected is not valid'. Seems I get this
> whenever I try to use custom paper styles. Any ideas?

No. Sorry.


Here is the code that should work:

procedure TForm1.Button1Click(Sender: TObject);
var
Device : array[0..255] of char;
Driver : array[0..255] of char;
Port : array[0..255] of char;
hDMode : THandle;
PDMode : PDEVMODE;
begin
Printer.PrinterIndex := Printer.PrinterIndex;
Printer.GetPrinter(Device, Driver, Port, hDMode);
if hDMode <> 0 then begin
pDMode := GlobalLock(hDMode);
if pDMode <> nil then begin

{Set to custom size}
pDMode^.dmFields := pDMode^.dmFields or
DM_PAPERSIZE or
DM_PAPERWIDTH or
DM_PAPERLENGTH;
pDMode^.dmPaperSize := DMPAPER_USER;
pDMode^.dmPaperWidth := 100 {SomeValueInTenthsOfAMillimeter};
pDMode^.dmPaperLength := 100 {SomeValueInTenthsOfAMillimeter};

{Set the bin to use (if you need to)}


pDMode^.dmFields := pDMode^.dmFields or DMBIN_MANUAL;
pDMode^.dmDefaultSource := DMBIN_MANUAL;

GlobalUnlock(hDMode);
end;
end;
Printer.PrinterIndex := Printer.PrinterIndex;
Printer.BeginDoc;
Printer.Canvas.TextOut(100,100, 'Test 1');
Printer.EndDoc;
end;

Joe

Hal

unread,
Mar 19, 1998, 3:00:00 AM3/19/98
to

Must be something with the printer or printer driver because when I set it
up in a 'canned program' it reacts the same way. Thanks for the help!

Hal


Duncan Murdoch

unread,
Mar 24, 1998, 3:00:00 AM3/24/98
to

(posted to borland.public.delphi.ide, and emailed)

>Here is the code that should work:
>

>procedure TForm1.Button1Click(Sender: TObject);
>var
>Device : array[0..255] of char;
>Driver : array[0..255] of char;
>Port : array[0..255] of char;
>hDMode : THandle;
>PDMode : PDEVMODE;
>begin
> Printer.PrinterIndex := Printer.PrinterIndex;
> Printer.GetPrinter(Device, Driver, Port, hDMode);

....

I have a similar problem. I want to set the bounding box when I print
to a Postscript printer in Encapsulated Postscript mode, with output
directed to a file. According to Win 3.1 docs, I'm supposed to set a
SET_BOUNDS escape, but I can't get that to work. (I get errror -1
from the Escape call.) I tried your code, and it doesn't work either.


Any suggestions for this problem??

Duncan Murdoch

Duncan Murdoch

unread,
Mar 24, 1998, 3:00:00 AM3/24/98
to

On Tue, 24 Mar 1998 12:29:58 GMT, dmur...@pair.com (Duncan Murdoch)
wrote:

>I have a similar problem. I want to set the bounding box when I print
>to a Postscript printer in Encapsulated Postscript mode, with output
>directed to a file. According to Win 3.1 docs, I'm supposed to set a
>SET_BOUNDS escape, but I can't get that to work. (I get errror -1
>from the Escape call.) I tried your code, and it doesn't work either.

Oops, forgot to mention that this is in Win95, and Delphi 3.

Duncan Murdoch

Joe C. Hecht (Borland)

unread,
Mar 24, 1998, 3:00:00 AM3/24/98
to

Forget SET_BOUNDS. If you are printing the entire job yourself
using passthrough, then simply reset the default Postscript
coordinate matrix, then send on the data.

Duncan Murdoch

unread,
Mar 24, 1998, 3:00:00 AM3/24/98
to

On Tue, 24 Mar 1998 12:35:54 -0600, "Joe C. Hecht (Borland)"
<jhe...@corp.borland.com> wrote:

>Forget SET_BOUNDS. If you are printing the entire job yourself
>using passthrough, then simply reset the default Postscript
>coordinate matrix, then send on the data.

I don't understand this; could you expand on it? I don't know how to
produce the Postscript commands for what I want; that's why I'm using
the Postscript driver. I'd like to just use standard TCanvas methods
to draw a graphic, and have it come out in EPS. Is it possible to mix
this kind of call in with what you're saying?

(I suppose I could just edit the file after it is produced; it's a
pretty minor change to set the bounding box, but that sounds pretty
kludgy.)

Duncan Murdoch

Joe C. Hecht (Borland)

unread,
Mar 25, 1998, 3:00:00 AM3/25/98
to

> I don't understand this; could you expand on it? I don't know how to
> produce the Postscript commands for what I want; that's why I'm using
> the Postscript driver. I'd like to just use standard TCanvas methods
> to draw a graphic, and have it come out in EPS. Is it possible to mix
> this kind of call in with what you're saying?

Ok, I though you were trying to embed an eps graphic
in the print job.

What you will want to do is change the settings in the Postscript
driver to output an EPS file. Thats it!

Duncan Murdoch

unread,
Mar 26, 1998, 3:00:00 AM3/26/98
to

On Wed, 25 Mar 1998 11:55:39 -0600, "Joe C. Hecht (Borland)"
<jhe...@corp.borland.com> wrote:

>Ok, I though you were trying to embed an eps graphic
>in the print job.
>
>What you will want to do is change the settings in the Postscript
>driver to output an EPS file. Thats it!

No, that gets back to my original question: the bounding box is
wrong. It's the whole page. According to the Win 3.1 docs, sending a
SET_BOUNDS escape is supposed to inform the driver of the correct
bounding box, but the Win32 docs don't mention this, and when I try
it, it doesn't work.

The other problem is that the driver automatically inserts code to
clear the whole page; even if I manually edit the bounding box, this
rect fill wipes out stuff nearby when I import the graphic. I can
also find the line that does that and edit it out, but this is a lot
of manual work; I'd rather it was done right in the first place!

Duncan Murdoch

0 new messages