LogicSoft.com.pl - systemy informatyczne

Drukowanie tekstu na drukarce igłowej w języku C#

Uwaga, otwiera nowe okno. PDFDrukujEmail

Podczas projektowania nowoczesnych systemów dla przedsiębiorstw posiadających wieloletnie doświadczenie w swym fachu niejednokrotnie zdarza się, że posiadają one na wyposażeniu drukarki igłowe, które świetnie sprawdzają się przy drukowaniu wielu dokumentów naraz, np.: faktur VAT. Jednak tryb drukowania z poziomu Windows umożliwia tylko i wyłącznie drukowanie "graficzne". Artykuł ten opisuje, w jaki sposób za pomocą języka C# i bibliotek znajdujących się w systemie operacyjnym drukować tekst na takiej drukarce igłowej w trybie "tekstowym", który jest znacznie szybszy niż wspomniany wcześniej "graficzny".

Niejednokrotnie zdarza się drukowanie różnego rodzaju raportów przy użyciu technologii .NET. Jednak problem pojawia się podczas drukowania na drukarkach igłowych, ponieważ domyślnie z poziomu Windows pracują one w formie "graficznej", co powoduje znacznie dluższy czas drukowania. Za pomocą kilku bibliotek i niewielkiej ilości kodu zaprezentowanych w tym artykule czytelnik będzie miał możliwość dowiedzenia się, jak w łatwy sposób drukować tekst znacznie szybciej.

Projekt zostanie wykonany w technologii WinForms. W oknie dialogowym powinna znajdować się lista dostępnych urządzeń drukujących, z którego uzytkownik powinien wybrać drukarke igłową, a następnie na nią wysłać odpowiednie ciągi znaków reprezentujące tekst do wydruku. Poniższy zrzut ekranu ukazuje wygląd okna dialogowego aplikacji.

Do obsługi wydruku na drukarce igłowej zostanie stworzona dodatkowa klasa WydrukLPT, której zadaniem będzie wysyłanie poszczególnych bajtów na port drukarki. Poniższy listing prezentuje kod tej klasy.

public class WydrukLPT
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class DOCINFOA
{
[MarshalAs(UnmanagedType.LPStr)]
public string pDocName;
[MarshalAs(UnmanagedType.LPStr)]
public string pOutputFile;
[MarshalAs(UnmanagedType.LPStr)]
public string pDataType;
}

[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 = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);

[DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndDocPrinter(IntPtr hPrinter);

[DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartPagePrinter(IntPtr hPrinter);

[DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndPagePrinter(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);

public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount)
{
Int32 dwError = 0, dwWritten = 0;
IntPtr hPrinter = new IntPtr(0);
DOCINFOA di = new DOCINFOA();
bool bSuccess = false;
di.pDocName = "C# Dot Matrix Test";
di.pDataType = "RAW";
if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
{
if (StartDocPrinter(hPrinter, 1, di))
{
if (StartPagePrinter(hPrinter))
{
bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
if (bSuccess == false)
{
dwError = Marshal.GetLastWin32Error();
}
return bSuccess;
}

public static bool SendStringToPrinter(string szPrinterName, string szString)
{
IntPtr pBytes;
Int32 dwCount;
dwCount = szString.Length;
pBytes = Marshal.StringToCoTaskMemAnsi(szString);
SendBytesToPrinter(szPrinterName, pBytes, dwCount);
Marshal.FreeCoTaskMem(pBytes);
return true;
}

public static bool SendFileToPrinter(string szPrinterName, string szFileName)
{
FileStream fs = new FileStream(szFileName, FileMode.Open);
BinaryReader br = new BinaryReader(fs);
Byte[] bytes = new Byte[fs.Length];
bool bSuccess = false;
IntPtr pUnmanagedBytes = new IntPtr(0);
int nLength;
nLength = Convert.ToInt32(fs.Length);
bytes = br.ReadBytes(nLength);
pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
Marshal.Copy(bytes, 0, pUnmanagedBytes, nLength);
bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, nLength);
Marshal.FreeCoTaskMem(pUnmanagedBytes);
return bSuccess;
}
}

Zadaniem zaprezentowanej powyżej klasy jest import sterowników obsługujących drukarke igłową, oraz obsługa przesyłania poszczególnych znaków na port drukarki a następnie drukowanie ich. Dodatkowo istnieje również możliwość wysyłania ciągów znaków (string) oraz odczytywania i wysyłania poszczególnych plików na port drukarki za pomocą metod SendStringToPrinter i SendFileToPrinter, jednak w tym przypadku będą one zbędne.

Poniżej zaprezentowany zostanie kod głównej klasy programu wczytujący dostępne drukarki z poziomu systemu Windows, a następnie zaprezentowana zostanie obsługa zdarzeń wybrania odpowiedniej drukarki i wysyłania na nią przykładowego tekstu, który następnie zostanie wydrukowany w formie tekstowej.

public partial class WybierzDrukarke : Form
{
public string wybranaDrukarka;
public WybierzDrukarke()
{
PrintDocument prtdoc = new PrintDocument();
string strDefaultPrinter = prtdoc.PrinterSettings.PrinterName;
InitializeComponent();
foreach (String printer in PrinterSettings.InstalledPrinters)
{
listBox1.Items.Add(printer.ToString());
if (printer == strDefaultPrinter)
{
listBox1.SelectedIndex = listBox1.Items.IndexOf(printer);
}
}
}

private void listBox1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
string pname = this.listBox1.SelectedItem.ToString();
myPrinters.SetDefaultPrinter(pname);
wybranaDrukarka = pname;
DrukujTekstNaDrukarce();
}
if (e.KeyCode == Keys.Escape)
{
this.Close();
}
}

public static class myPrinters
{
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetDefaultPrinter(string Name);
}

private void listBox1_MouseDoubleClick(object sender, System.Windows.Forms.MouseEventArgs e)
{
string pname = this.listBox1.SelectedItem.ToString();
myPrinters.SetDefaultPrinter(pname);
wybranaDrukarka = pname;
DrukujTekstNaDrukarce();
}

private void DrukujTekstNaDrukarce()
{
Encoding dstEncoding = Encoding.GetEncoding("ibm852");
byte[] dstBytes = dstEncoding.GetBytes("Przykladowy tekst, ktory ma byc wydrukowany na drukarce iglowej.");
GCHandle pinnedArray = GCHandle.Alloc(dstBytes, GCHandleType.Pinned);
IntPtr pointer = pinnedArray.AddrOfPinnedObject();
WydrukLPT.SendBytesToPrinter(wybranaDrukarka, pointer, dstBytes.Length);
}
}

Powyższy listing w pierwszej kolejności prezentuje konstruktor, którego zadaniem jest wczytanie dostępnych z poziomu systemu Windows dostępnych drukarek. Następnie opisany jest kod odpowiedzialny za metody obsługi zdarzeń naciśnięcia klawisza enter oraz podwójnego przyciśnięcia na wybranej drukarce, co spowoduje ustawienie jej jako domyślnej drukarki (importowana klasa myPrinters) oraz wysłanie na nią tekstu "Przykladowy tekst, ktory ma byc wydrukowany na drukarce iglowej." w formie bajtów, które następnie zostanie wydrukowany w formie tekstu. W tym przypadku nie została zaimplementowana obsługa wybrania innej drukarki, niż igłowa. Jednak nie jest to zbyt dużym problemem. Poniżej znajduje się okno przykładowego działania programu.

Po wybraniu odpowiedniej drukarki i potwierdzeniu podwójnnym kliknięciem lub naciśnieciem klawisza enter na wybraną drukarkę igłową zostanie wysłany przykładowy tekst, który następnie zostanie wydrukowany. W niedługim czasie postaram się umieścić artykuł opisujący, w jaki sposób drukować grafikę na drukarce igłowej za pomocą kodów sterujących (w tym przypadku kodów do drukarki OKI ML3320). Wiadomym jest, że nie będzie ona zbyt dobrej jakości, jednak wydaje mi się, że znajdą się osoby chętne do przestudiowania tego tekstu.

W razie jakichkolwiek pytań, zapraszam do działu kontakt.