Issue 4 - 08 September 2009 Go Back
Recently I was working on some projects for production to built end of line testers (EOLT) applications. In production environment it’s very important to find a way to avoid any interference from operators. If a printer requires reinstallation it’s critical to continue the production process with minimum delay.
There were a number of printers connected to the same computer to print labels and reports. In addition to that the system had to recognise if the required printers are connected and switched on. It would be messy and slow for an operator to choose printers every time to print labels or test reports. So the only solution was to get the direct control on all the connected printers.
My first project with EOLT was done using Borland Delphi and second time I used C#. Please note that you need to play with printer settings, using control panel in order to finalised correct settings for the connected printers. You need to set printer names, paper sources and so on. after the initial manual setup is complete you can read and save all settings and restore them at any time. All you need to use are existing functions like GlobalLock, GlobalUnlock, SetPrinter, GetPrinter, OpenPrinter, ClosePrinter and WritePrinter.
For my first Delphi project I read all settings and hardcoded them into the application. Also I added the register keys as the following "\System\CurrentControlSet\Control\Print\Printers\PassLabels", where "PassLabels" is the name of the printer attached to computer. So far it is running for over two years without any complaints. For my second C# project I’ve decided to read and save all settings into a text file. Every time the main application starts, it reads the file and updates all printer settings before further processing. In my view having an external file gives more freedom in case you need to replace printer with different type. It would require just a replacing of the txt file with correct settings for the new printer and there are no need to recompile the entire application. Anyway,the time will tell which way is better and more practical.
In case if you like to compile the code you will need to add uses Printers, Registry, WinSpool And define the following in your Form definitions LabelPrinter : TPrinter;
The following procedure written in Borland Delphi and shows how to use the register keys and how to print any text at any position on the label or page.
procedure PrintLabel;
var
myYear, myMonth, myDay : Word;
PrintStr : String;
PrinterIsOnLine : Boolean;
begin
PrinterIsOnLine:= false;
// here we check if printer is ready by reading register key.
with TRegistry.Create do
try
RootKey := HKEY_CURRENT_CONFIG;
if OpenKey('\System\CurrentControlSet\Control\Print\Printers\LabelsPrinter', False) then
PrinterIsOnLine:= ReadBool('PrinterOnLine');
CloseKey;
finally
Free;
end;// printer is off or not connected, so you need to take care of this situation in your own application.
if PrinterIsOnLine = false then
exit;// here we are printing our information by setting positions of each line on the label ( page ). It is //just example and positions are hard coded.
with LabelPrinter do
begin
// Start printing
BeginDoc;
// Set up font and size
Canvas.Font.Name := ‘Arial’;
Canvas.Font.Size := 16;
// Print String
Canvas.TextOut(14, 10, 'PASS');
// Get date
DecodeDate(Date , myYear, myMonth, myDay);
PrintStr := IntToStr(myDay) + '/' + IntToStr(myMonth) + '/' + IntToStr(myYear);
// Set up font size
Canvas.Font.Size := 10;
// Print String
Canvas.TextOut(280, 20, PrintStr);// Set up font size
Canvas.Font.Size := 6;
// Print String
Canvas.TextOut(14, 100, 'Part Number : ' + ‘123123123’ ); //just an example
Canvas.TextOut(14, 130, ‘SN123123123’); ); //just an example// Set up new font for bar code and define font size
Canvas.Font.Name := 'IDAutomationHC39M';
Canvas.Font.Size := 10;
// Print String
Canvas.TextOut(80, 240, '*' + 'PASS' + '-' + ‘123123123’ + '*'); ); //just an axample// Finish printing
EndDoc;
end;
end;
The following procedure shows an example of how to read and control printers from your application in Borland Delphi. You can write your own procedure, but here I do it on Form Create event.
procedure TForm2.FormCreate(Sender: TObject);
var
myYear, myMonth, myDay : Word;
j: Integer;
Device : PChar;
Driver : Pchar;
Port : Pchar;
HdeviceMode: Thandle;
myPrinterName : string;
DevMode: PDeviceMode;
Begin
// here the printer name defined in control panel on computer
myPrinterName := ‘LabelsPrinter’;Printer.PrinterIndex := -1;
getmem(Device, 255) ;
getmem(Driver, 255) ;
getmem(Port, 255) ;LabelPrinter:= TPrinter.create;
// next code shows how to read and change printer settings, using GlobalLock, SetPrinter,
// GetPrinter and GlobalUnlock functions.for j := 0 to Printer.printers.Count-1 do
begin
if Printer.printers[j] = myPrinterName then
begin
LabelPrinter.printerindex := j;
LabelPrinter.GetPrinter(Device, Driver, Port, HdeviceMode) ;
DevMode := GlobalLock(HdeviceMode);
DevMode^.dmDeviceName := 'PassLabels';
DevMode^.dmSpecVersion := 1025;
DevMode^.dmDriverVersion := 1543;
DevMode^.dmSize := 156;
DevMode^.dmDriverExtra := 1296;
DevMode^.dmFields := 75023;
DevMode^.dmOrientation := 1;
DevMode^.dmPaperSize := 256;
DevMode^.dmPaperLength := 478;
DevMode^.dmPaperWidth := 736;
DevMode^.dmScale := 100;
DevMode^.dmCopies := 1;
DevMode^.dmDefaultSource := 256;
DevMode^.dmPrintQuality := 203;
DevMode^.dmColor := 1;
DevMode^.dmDuplex := 1;
DevMode^.dmYResolution := 203;
DevMode^.dmTTOption := 1;
DevMode^.dmCollate := 0;
DevMode^.dmFormName := 'USER';
DevMode^.dmLogPixels := 0;
DevMode^.dmBitsPerPel := 0;
DevMode^.dmPelsWidth := 0;
DevMode^.dmPelsHeight := 0;
DevMode^.dmDisplayFlags := 0;
DevMode^.dmDisplayFrequency := 0;
DevMode^.dmICMMethod := 2;
DevMode^.dmICMIntent := 1;
DevMode^.dmMediaType := 1;
DevMode^.dmDitherType := 0;
DevMode^.dmICCManufacturer := 0;
DevMode^.dmICCModel := 0;
DevMode^.dmPanningWidth := 0;
DevMode^.dmPanningHeight := 0;
//set a printer settings
LabelPrinter.SetPrinter(Device, Driver, Port, HdeviceMode);//unlock a device
GlobalUnlock(HdeviceMode);
end;
end;
end;
The next example of the code is written in C# and as I mentioned above it reads printer settings, saves them into a text file and restore them on every start up of the main application. I tested the code with a network printer.
using System;
using System.Drawing.Printing;
using System.Runtime.InteropServices;
using System.IO;
using System.Drawing;namespace mySpace
{
[StructLayout(LayoutKind.Sequential)]
public struct DEVMODE
{
public const int CCHDEVICENAME = 32;
public const int CCHFORMNAME = 32;[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
public string dmFormName;
public short dmLogPixels;
public int dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;public int dmPositionX;
public int dmPositionY;
}class myPrinter
{
[DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);[DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool ClosePrinter(IntPtr hPrinter);[DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);
[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalFree(IntPtr handle);[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalLock(IntPtr handle);[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalUnlock(IntPtr handle);public static string testReport;
public static string prnName;// somewhere in your code you will need to add
// myPrinter.prnSettings = new PrinterSettings();
public static PrinterSettings prnSettings;public static bool GetPrinterSettings()
{
IntPtr hDevMode;
IntPtr pDevMode;
DEVMODE devMode;
IntPtr hPrinter = new IntPtr(0);
//it is just an axample of network printer
prnSettings.PrinterName = "\\\\ipp://myprint.mydomain.com.au\\myPrinter1";
// Open the printer.
if (OpenPrinter(prnSettings.PrinterName.Normalize(), out hPrinter, IntPtr.Zero))
{
// Get the default printer settings
hDevMode = prnSettings.GetHdevmode(prnSettings.DefaultPageSettings);// Obtain a lock on the handle
pDevMode = GlobalLock(hDevMode);// Marshal the memory into our DEVMODE
devMode = (DEVMODE)Marshal.PtrToStructure(pDevMode, typeof(DEVMODE));
// file stream
using (FileStream fs = new FileStream("C:\\My_PRN_SETTINGS\\prnSettings.txt", FileMode.Create))
{
for (int i = 0; i < devMode.dmSize + devMode.dmDriverExtra; ++i)
{
fs.WriteByte(Marshal.ReadByte(pDevMode, i));
}
}// Unlock the handle GlobalUnlock(hDevMode);
GlobalFree(hDevMode);
}
else
{
return false;
}return true;
}public static bool UploadPrinterSettings()
{
IntPtr hDevMode;
IntPtr pDevMode;
byte[] savedDevMode;
Stream savedDevStream;IntPtr hPrinter = new IntPtr(0);
//it is just an axample of network printer
prnSettings.PrinterName = "\\\\ipp://myprint.mydomain.com.au\\myPrinter1";
// Open the printer.
if (OpenPrinter(prnSettings.PrinterName.Normalize(), out hPrinter, IntPtr.Zero))
{
// get the current DEVMODE position in memory
hDevMode = prnSettings.GetHdevmode(prnSettings.DefaultPageSettings);// Obtain a lock
pDevMode = GlobalLock(hDevMode);// Load the saved DEVMODE
savedDevStream = new FileStream("C:\\My_PRN_SETTINGS\\prnSettings.txt", FileMode.Open, FileAccess.Read);
savedDevMode = new byte[savedDevStream.Length];
savedDevStream.Read(savedDevMode, 0, savedDevMode.Length);
savedDevStream.Close();
savedDevStream.Dispose();// Overwrite the current DEVMODE
for (int i = 0; i < savedDevMode.Length; ++i)
{
Marshal.WriteByte(pDevMode, i, savedDevMode[i]);
}// It is good to go. All done
GlobalUnlock(hDevMode);// upload our printer settings to use the one we just overwrote
prnSettings.SetHdevmode(hDevMode);GlobalFree(hDevMode);
prnName = prnSettings.PrinterName;
}
else
{
return false;
}return true;
}public static bool StartPrintintg()
{
if( UploadPrinterSettings() )
{
PrintDocument prnDoc = new PrintDocument();prnDoc.PrinterSettings = prnSettings.DefaultPageSettings.PrinterSettings;
prnDoc.PrintPage += new PrintPageEventHandler(PrintPage);prnDoc.Print();
prnDoc.Dispose();
}
else
{
return false;
}return true;
}public static void PrintPage(object sender, PrintPageEventArgs e)
{
int y;
// Set Font atributes
Font myFont = new Font("IDAutomationHC39M", 16, FontStyle.Bold);
y = e.MarginBounds.Y;
// Print string
e.Graphics.DrawString("*testing the printers*", myFont, Brushes.DarkRed, e.MarginBounds.X, y);
myFont.Dispose();
// Set another Font and it’s attributes
myFont = new Font("Free 3 of 9 Extended", 16, FontStyle.Bold);
y += 200;
// Print String
e.Graphics.DrawString("Pass", myFont, Brushes.DarkRed, e.MarginBounds.X, y);
myFont.Dispose();
}
}
}Let me give you some recommendations on how you can test C# code from you application. Add a couple of buttons and on click event call
myPrinter.StartPrintintg();and on second button click call
if (myPrinter.GetPrinterSettings())
{
// Now you can set breakpoint here and go to control panel and
// change your printer settings to something completely wrong.
Application.DoEvents();
}
Do not forget to add somewhere
myPrinter.prnSettings = new PrinterSettings();
I wish you good luck and hope it will help you in your design. It worked for me.