Delphi printing to Zebra printer - delphi

I have written a function that prints out ZPLII data to a Zebra printer as shown below
var
St : string;
StartDocument(DocName);
StartPage;
Try
For I:=0 to DataToPrint.Count-1 do
Begin
St:=FormatPrintLine(I);
Try
Escape(PrinterHandle,PASSTHROUGH,Length(St),PAnsiChar(St),Nil);
Except on E:Exception do
begin
GetWin32ApiErrorMessage(FErrorCode,FErrorText);
Raise EPrinter.CreateFmt('Printer Write Error %d'+#13+
'While Printing To '+PrinterName+#13+
ErrorText,[ErrorCode]);
end;
End;
end;
Finally
EndPage;
EndDocument;
I have tested the label data by using the command prompt to print it from a text file and the label prints out correctly but i cannot print it from my application. If i pause the printer i can see the job gets sent to the printer and the size of the job is 2.12Kb, roughly the size the label should be, but nothing prints out. The light on the Zebra printer for data lights up but nothing will print. I have tried this with two of the Zebra printers we own, so it is not a printer issue. My guess at this point is that maybe the program isn't sending the entire label data to the printer and the end is never received but when i trace through the send request, everthing is sent properly. the printer also shows the job has 0/0 pages for the label but i cannot understand why it is not sending the label. Is there something special that needs to go at the end of the label data besides the ^XZ termination character? I am also using Delphi XE3 if that helps.

Thanks to everyone's help I was able to successfully print my labels using the following changes:
St: AnsiString;
...
StartDocument(DocName);
StartPage;
Try
For I:=0 to DataToPrint.Count-1 do
Begin
St:=FormatPrintLine(I);
Try
WritePrinter(PrinterHandle,PChar(St),Length(St),N);
Except on E:Exception do
begin
GetWin32ApiErrorMessage(FErrorCode,FErrorText);
Raise EPrinter.CreateFmt('Printer Write Error %d'+#13+
'While Printing To '+PrinterName+#13+
ErrorText,[ErrorCode]);
end;
End;
end;
Finally
EndPage;
EndDocument;
I also had to change to writeprinter instead of escape. Escape did not print anything out after i change st to type AnsiString but writeprinter was successful.

Related

Delphi TPrinter and error 217

Can anybody explain how this should work (Delphi XE2 running on Windows10)?
In a minimalist application - a form with two buttons - with the following code:
procedure TForm2.Button1Click(Sender: TObject);
begin
Printer.BeginDoc;
Printer.Canvas.TextOut(10,10,'Hello World');
Printer.EndDoc;
end;
procedure TForm2.Button2Click(Sender: TObject);
var
MyPrinter : TPrinter;
begin
MyPrinter := Printer;
MyPrinter.BeginDoc;
MyPrinter.Canvas.TextOut(10,10,'Hello World');
MyPrinter.EndDoc;
MyPrinter.Free;
end;
If I click Button1, the program prints a 'Hello World' at my default printer, and closes normally when I close it (with the close button). If instead, I click Button2, the program prints an identical page but now when I close with the close button I get an Error 217 message.
I can't find clarification in the Delphi documentation regarding exactly how to use the Printer function and TPrinter variables. I am quite happy using a derivative of the Button1 technique to print - if it is confirmed that this is the 'bullet-proof' way to go, but would like to understand why the Button2 method doesn't work. Should I just assume that it is not my responsibility to free any TPrinter object I instantiate, or is there a clearer explanation?
When you are running this code:
MyPrinter := Printer;
MyPrinter.BeginDoc;
MyPrinter.Canvas.TextOut(10,10,'Hello World');
MyPrinter.EndDoc;
MyPrinter.Free;
Printer is deleted on function exit. So when appication is terminating, it tries to delete it again, and you get an error. More than that, if you try to run this code second time, it will raise as well. MyPrinter is just a pointer to the global TPrinter object, returned by the Printer function. You should not delete things, that you have not created.
Documentation of the Printer function:
Returns a global instance of TPrinter to manage interaction with the
printer.
Printer creates an instance of a global TPrinter object the first time
it is called. Use the Printer function to print using the TPrinter
object.
Note: The global TPrinter object is freed automatically when the
application shuts down. After a call to SetPrinter, the printer that
is returned is not automatically freed. It is the caller's
responsibility to either free the return value, or replace it using
another call to SetPrinter and to free the substitute printer that the
second SetPrinter call returns.

Zebra Printer direct communication

Based on this question I have implemented the following code to send direct commands to my Zebra TLP2844
var
cmm: AnsiString;
i: integer;
begin
commands.saveToFile('path\to\a\file');
Printer.BeginDoc;
cmm := '';
for i := 0 to commands.Count-1 do
cmm := cmm + commands[i] + #10;
Escape(Printer.Canvas.Handle, PASSTHROUGH, Length(cmm), PAnsiChar(cmm), nil);
Printer.EndDoc;
end;
commands is a TSringList containing all the commands I want to send to the printer.
Note that I save all the commands to a text file.
Well, if I send this text file to print, via the driver preferences, using tools -> Action -> Send File, it prints perfectly.
If I use the code above, it spits some extra rows of labels after printing.
It shows me, obviously, that I am doing something wrong here, but I can't figure out what.
What I have tried
Send commands one by one and not concatenating them like in the code. Results: Nothing gets printed.
Changing #10 for a #13#10. Results: Same crazy behaviour (indeed Zebra EPL documentatins says it will ignore any #13 it finds)
What else should I try in order to send to the printer the commands the exact same way Zebra's tool does?
AFAIK you need to format the buffer as expected by the ExtEscape() API layout. I never used Escape(), but ExtEscape() - and it worked with a Zebra printer.
Here is what the MSDN doc states:
lpszInData [in] A pointer to the input structure required for the
specified escape. The first word in the buffer contains the number of
bytes of input data. The remaining bytes of the buffer contain the
data itself.
So you may code this as such:
cmm := '00'; // reserve space for the initial `word`
for i := 0 to commands.Count-1 do
cmm := cmm + commands[i] + #10;
pword(cmm)^ := length(cmm)-2; // store the length
if ExtEscape(Printer.Canvas.Handle, PASSTHROUGH, Length(cmm), pointer(cmm), 0, nil)<0 then
raise Exception.Create('Error at printing to printer');
Printer.EndDoc;
Be aware that if your command is not well formatted (e.g. missing chars), it may just create an out of memory error in the printer spooler - yes, I've seen that! In this case, you may have to kill then restart the Printer Spooler service... fix your code... and try again...
And do not forget to put the ESC character at the beginning of each of your commands[], as requested by the Zebra doc.
you can use this procedure: where the LabelFile is the full path of the label file,we are using this code and works with generic text driver printer and the printer is set as the default printer. it works with zebra printer and windows xp operating system.
https://stackoverflow.com/a/27647044/2977139
i hope this will help you.
If you want to use the Windows printer driver, you should use WritePrinter defined un the WinSpool unit. If I see this correctly, the TPrinter object from the Printers unit doesn't expose it's FPrinterHandle member, so you might need to use OpenPrinter and ClosePrinter yourself.
Having worked with MarkPoint printers at work, which are somewhat similar to Zebra printers: if the printer is connect to a serial port, I would warmly suggest to try and access the printer directly by connecting to the serial port with one of the several components available.

Sending commands directly to Zebra EPL

I am trying to send commands directly to a Zebra TLP2844 printer. I followed the suggestion made here and my final code came to be as follows:
var
cm: String;
p: TPrinter;
i: integer;
begin
p := Printer;
p.BeginDoc;
for i := 0 to memo1.Lines.Count-2 do
begin
cm := memo1.Lines[i];
if Escape(p.Canvas.Handle,
PASSTHROUGH,
Length(cm),
PAnsiChar(cm),
nil) = 0 then
ShowMessage('Command error: ' + IntToStr(GetLastError));
end;
p.EndDoc;
end;
The content of memo1 is (first line is empty) as purposed here:
N
q609
Q203,26
B26,26,0,UA0,2,2,152,B,"603679025109"
A253,26,0,3,1,1,N,"SKU 6205518 MFG 6354"
A253,56,0,3,1,1,N,"2XIST TROPICAL BEACH"
A253,86,0,3,1,1,N,"STRIPE SQUARE CUT TRUNK"
A253,116,0,3,1,1,N,"BRICK"
A253,146,0,3,1,1,N,"X-LARGE"
P1,1
The commands don't seem to be properly received or interpreted by the printer. I checked that the printer is in Page Mode (EPL2), with the suggested code I am able to open the printer handle. But nothing is printed, only a new line of labels is feeded.
I tried to completely change the commands to something obviously wrong and the behaviour is the same.
What else should I be looking to get things printed?
Most printers that take raw commands require a prefix (starting sequence of characters) and suffix (ending sequence of chars) wrapping each command. I don't know what the prefix and suffix are for the Zebra, but the documentation should tell you.
Just add a pair of constants to define the prefix and suffix, and add them to your command before sending it.
The other issue might be that you're reading the content of your commands from a TMemo, which in Delphi 2009 and higher contains Unicode strings. You're then casting them down to PAnsiChar, which may be truncating the content. Do the conversion ahead of time by defining cm as an AnsiString, and then assigning to it first (as you are) before typecasting to pass to the Escape function. I've done this in my code to illustrate it.
var
cm: AnsiString;
p: TPrinter;
i: integer;
const
ZPrefix = AnsiString('$('); // Replace values for each of these with what
ZSuffix = AnsiString(')$'); // your documentation says you should use
begin
p := Printer;
p.BeginDoc;
for i := 0 to memo1.Lines.Count-2 do
begin
cm := ZPrefix + memo1.Lines[i] + ZSuffix;
if Escape(p.Canvas.Handle,
PASSTHROUGH,
Length(cm),
PAnsiChar(cm),
nil) = 0 then
ShowMessage('Command error: ' + IntToStr(GetLastError));
end;
p.EndDoc;
end;
I program in php which is like C
I can send things to the printer just fine
my code looks like your code the only thing is I am not sure how your programming language handles the newline in php it's \n at the end of each line
if the newline is not there the print job will not print
and if the " are not sent it will not print
your EPL looks fine and should print
there is somewhere on the zebra web site a download where you can send commands to a printer which is hooked up to your computer by USB cable
think it is called Zebra Setup Utilities

Delphi: PJL commands to printer and reading the responses back

Im struggling with getting information back from the printer when sending PJL to the printer. The printer supports PJL and it is a USB printer. Now getting information / examples seems to be a problem or I'm looking at the wrong places. I know on MSDN there are a lot of information, but I've tried everything I got there, from docinfo's to write/read printers and nothing seems to work.
Some people say you can use writeprinter and readprinter. I've tried this, when I writeprinter, the printer seems to "do" something, but readprinter returns or errors or blanks. Now I think this could be because the print driver is "locking" the port, so you cant read information back from it?
The other option I saw somewhere is to use writefile and readfile. Here you would get the physical port part for the printer, for example '\?\USB#VID_05CA&PID_0403#S5208603411#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}'. Then you change the port to "FILE". Use the writefile and readfile with the path as the physical path from above. Get the information you need and then set the port back to the original port. Tried this as well, also getting errors.
Im just trying to do a simple #PJL INFO PAGECOUNT (I left out the escape chars etc, etc). The string is correct, because using the string on networked printers, its working 100% and I can get the information. But local printers is a problem.
Is there anyone that has this working or a working example? Any help would be much appreciated.
PS: Below is 1 of the 100's of examples I've tried. This is the writeprinter example:
procedure TForm1.Button5Click(Sender: TObject);
Const
Defaults: TPrinterDefaults = (
pDatatype : Nil;
pDevMode : nil;
DesiredAccess : PRINTER_ACCESS_USE or PRINTER_ACCESS_ADMINISTER ) ;
Var
Device : array[0..255] of char;
FPrinterHandle:THandle;
DocInfo1: TDocInfo1;
Buffer, Buffer2:PChar;
Written, Len:Cardinal;
i: Integer;
sPath: String;
Begin
StrCopy(Device,PChar('RICOH Aficio SP 4210N PCL 6'));
OpenPrinter(#Device, FPrinterHandle, #Defaults);
DocInfo1.pDocName := 'test';
DocInfo1.pOutputFile := Nil;
DocInfo1.pDatatype := 'RAW';
StartDocPrinter(FPrinterHandle, 1, #DocInfo1);
StartPagePrinter(FPrinterHandle);
Buffer := #27+'%-12345X#PJL COMMENT'+#13+#10+'#PJL INFO PAGECOUNT'+#13+#10+#27+'%-12345X';
WritePrinter(FPrinterHandle,#Buffer,Length(Buffer), Written);
EndPagePrinter(FPrinterHandle);
EndDocPrinter(FPrinterHandle);
// everithing is OK here, BUT
ReadPrinter(FPrinterHandle, #Buffer2, Length(Buffer2), len );
end;
Check http://www.undocprint.org/winspool/tips_and_tricks for an explanation of what to do, and some sample C code.
Even with this code the chances of this working for you are minimal. To be able to read back from the printer, the port monitor must support bidirectional mode, and the standard USB port monitor does not.
Also, in your code above, Buffer2 passed to ReadPrinter() is not correct. You need to pre-allocate buffer space, and then pass the address of the buffer, not the address of the pointer to the buffer...
var
Buffer2 : array[0..255] of Char;
begin
...
ReadPrinter( FPrinterHandle, #Buffer2[0], Length(Buffer2), len );
end;

determine if another application is busy

How do I check if another application is busy?
I have a program that sends text to a console. The text that I will send contains #13 char (e.g. ls#13cd documents#13dir). In other words I want to send many commands at one time and the console will process them one by one. I am sending the text character by character. Sometimes the console only executes ls and cd documents. I think maybe this is because my program continuously sends character even if the console is busy, in which case the console does not receive incoming characters.
This is my code:
procedure TForm1.SendTextToAppO(Str: String; AHandle: Integer);
var
iWindow, iPoint, i: Integer;
SPass: PChar;
sList: TStringList;
begin
sList := TStringList.Create;
ExtractStrings([#13],[' '],PChar(Str),sList);
iWindow := AHandle;// AHandle is the handle of the console
iPoint := ChildWindowFromPoint(iWindow, Point(50,50));
for i:=0 to sList.Count-1 do begin
SPass := PChar(sList[i]);
try
while(SPass^ <> #$00) do begin
SendMessage(iPoint,WM_CHAR,Ord(SPass^),0);
Inc(SPass);
end;
SendMessage(iPoint,WM_KEYDOWN,VK_RETURN,0);
except
// do nothing;
end;
end;
end;
I am using Delphi 7.
If I interpret you question correctly you are sending the text to some sort of shell/command line interpreter and you want it to execute your commands.
Usually command line interpreters output a certain prompt (like $ on a Linux system or C:\ for DOS) that indicate that they can accept new commands. You need to read the output to wait for the appropriate prompt before you send another command. If you don't your sent text will be consumed as input by the currently running command (like you experienced).
lothar is on the right track; what you want to do is, instead of using ShellExecute, use CreateProcess. Look around Stack Overflow and Google for "Console Redirection" - that'll get you what you're looking for.
I think I understand what's going on, not that I have a fix for it:
You send a command to the console. While the command is running that program will receive the keys you send.

Resources