Introduction
I was looking for a long time to send escape-sequences to a printer while printing with the normal System.Drawing.Printing objects. This can be useful if you want to send faxes and tell the fax-drive with an escape sequence where you want to send the fax. You can also print barcodes with it. Most solutions that I found were about printing the whole page with userdefined commands. Here's a very simple solution about How to use GDI+ for printing and still have the possibility of sending userdefined PrinterCommands.
The Code
The code is very simple. There are two classes.
The first class is called EscapePrintHelper and looks like this:
public class EscapePrintHelper
{
[DllImport("gdi32.dll", EntryPoint="Escape",
CharSet=CharSet.Auto, SetLastError=true)]
private static extern int Escape
(IntPtr Hdc, int nEscape, int ncount, IntPtr inData, IntPtr outData);
private const int PASSTHROUGH = 19;
private IntPtr Handle;
private Graphics graphics;
public EscapePrintHelper(Graphics Graphics)
{
graphics = Graphics;
Handle = Graphics.GetHdc();
Graphics.ReleaseHdc(Handle);
}
public bool SendPassThroughExt(string PassthroughData)
{
return sendPassThrough(PassthroughData,Handle);
}
private bool sendPassThrough(string Passthroughdata, IntPtr handle)
{
IntPtr grp = handle;
bool RetVal = false;
IntPtr pData = string2Global(Passthroughdata,true);
try
{
int id = Escape(grp,PASSTHROUGH,0,pData,IntPtr.Zero);
RetVal = id > 0;
}
finally
{
Marshal.FreeHGlobal(pData);
}
return RetVal;
}
public bool SendPassThrough(string PassthroughData)
{
bool RetVal = false;
IntPtr grp = graphics.GetHdc();
try
{
RetVal = sendPassThrough(PassthroughData, grp);
}
finally
{
graphics.ReleaseHdc(grp);
}
return RetVal;
}
private IntPtr string2Global(string Data, bool includeSize)
{
int l = Data.Length;
int offset = 0;
if (includeSize)
{
l+=2;
offset = 2;
}
IntPtr RetVal = Marshal.AllocHGlobal(l);
short s = (short)Data.Length;
byte[] buf = new byte[l];
if (includeSize)
{
buf[1] = (byte)(s >> 8);
buf[0] = (byte)s;
}
System.Text.Encoding.Default.GetBytes(Data,0,Data.Length,buf,offset);
for (int i = 0; i< buf.Length; i++)
{
Marshal.WriteByte(RetVal,i,buf[i]);
}
return RetVal;
}
}
This class allows me to bypass the printer. So you could use this class without the second class, which is only a derived PrintDocument class that has a method called SendPassThroughCommand.
public class EscapeEventArgs:EventArgs
{
private EscapePrintDocument doc;
public EscapePrintDocument Doc
{
get
{
return doc;
}
}
private EscapeEventArgs()
{
}
public EscapeEventArgs(EscapePrintDocument Doc)
{
doc = Doc;
}
}
public delegate void EscapeEventHandler(object sender, EscapeEventArgs e);
public class EscapePrintDocument:System.Drawing.Printing.PrintDocument
{
public event EscapeEventHandler AfterPrint;
private EscapePrintHelper commandSender;
private bool printing;
public void SendPassThroughCommand(string PassThroughCommand)
{
if (printing)
{
commandSender.SendPassThrough(PassThroughCommand);
}
else
{
commandSender.SendPassThroughExt(PassThroughCommand);
}
}
protected override void OnPrintPage(System.Drawing.Printing.PrintPageEventArgs e)
{
printing = true;
commandSender = new EscapePrintHelper(e.Graphics);
try
{
base.OnPrintPage(e);
}
finally
{
printing = false;
}
}
protected virtual void onAfterPrint()
{
if (AfterPrint != null)
{
AfterPrint(this, new EscapeEventArgs(this));
}
}
protected override void OnEndPrint(PrintEventArgs e)
{
try
{
onAfterPrint();
}
finally
{
base.OnEndPrint (e);
}
}
}
Its usage is now really simple. Just create an EscapePrintDocument and print it. While printing, use the SendPassThroughCommand to send EscapeSequences or things like that:
private void TestPrint()
{
PrintDialog dlg = new PrintDialog();
EscapePrintDocument test = new EscapePrintDocument();
dlg.Document = test;
if (dlg.ShowDialog() == DialogResult.OK)
{
test.PrintPage +=new PrintPageEventHandler(test_PrintPage);
test.Print();
}
}
private void test_PrintPage(object sender, PrintPageEventArgs e)
{
EscapePrintDocument doc = sender as EscapePrintDocument;
EscapePrintHelper hlp = new EscapePrintHelper(e.Graphics);
SizeF pf = e.Graphics.MeasureString(textBox1.Text,new Font("Arial",12));
RectangleF rf = new RectangleF(new PointF(30,30),pf);
e.Graphics.DrawString("Blah!",new Font("Arial",12),Brushes.Black,rf);
if (doc != null)
{
doc.SendPassThroughCommand
("\x1b[COMMAND]NUMMER YourFaxNumber\x1b[END COMMAND]");
}
e.HasMorePages = false;
}
This example is tested with TobitFax (if anybody knows that one:)).
If you wish to send Passthrough commands after the last page, you can add a handler to the AfterPrint event and send the command there.
Summary
I hope this example is useful for somebody. I know that the implementation is not perfect, especially for the string2Global method which I tried to implement with a struct first, but I couldn't make it work.