Rubrik: Drucker | VB-Versionen: VB2008, VB2010, VB2012 | 08.06.13 |
Kassenbon-Drucker mit VB.Net oder C# per ESC/POS-Befehle ansprechen Dieser Tipp soll zeigen, wie ein per USB angeschlossener Bondrucker ohne Zusatzsoftware und Treiber über den ESC/POS-Kommandosatz aus VB.Net oder C# heraus angesprochen werden kann. | ||
Autor: Konstantin Preißer | Bewertung: | Views: 34.334 |
www.preisser-it.de | System: WinXP, Win7, Win8, Win10, Win11 | kein Beispielprojekt |
Man kennt sie aus Supermarkt- und Discounter-Kassen: Kassenbons mit einer Auflistung der gekauften Artikel, die von Bondruckern auf Thermopapier (meistens 58 mm oder 80 mm breit) gedruckt werden.
In diesem Tipp soll gezeigt werden, wie man selbst einen solchen Bondrucker über den ESC/POS-Kommandosatz ansprechen kann, ohne zusätzliche Software oder Treiber installieren zu müssen. Die Codebeispiele sind für VB.Net, können aber natürlich auch mit einem Code-Converter nach C# konvertiert und verwendet werden (sie wurden ursprünglich auch in C# erstellt).
Als Beispiel wird hier der Bondrucker PRP-058 (mit paralleler Schnittstelle sowie USB-Adapter) verwendet, der für ungefähr 100 € zu haben ist. Es können aber selbstverständlich auch andere ESC/POS-kompatible Drucker wie die der Epson TM-T88-Serie verwendet werden.
1. Installation des Druckers
Da nahezu alle aktuellen Notebooks und auch die meisten aktuellen PCs keinen parallelen Anschluss mehr haben, benötigen wir entweder einen Bondrucker mit USB-
Anschluss, oder einen Bondrucker mit parallelem Anschluss sowie einen USB-nach-Parallel-Adapter. In diesem Tipp wird als Beispiel der Bondrucker PRP-058 mit
parallelem Anschluss und USB-nach-Parallel-Adapter (Achtung: [bold]keine 36-polige Centronics-Schnittstelle[/bold], sondern 25-poliger D-Sub-Stecker)
verwendet, wie auf dem Bild zu sehen:
Nachdem der USB-nach-Parallel-Adapter an den Drucker angeschlossen und der Drucker eingeschaltet wurde, können wir den USB-Stecker an den PC oder das Notebook anschließen. Windows müsste nun eine Treiberinstallation für eine neue Hardware ("USB-Druckerunterstützung" oder "IEEE-1284 Controller") melden.
Falls der verwendete der Drucker Plug-&-Play-fähig ist, müsste sich nun ebenfalls ein Dialog zur Treiberinstallation für den Drucker öffnen. Da der RPR-058 jedoch kein Plug & Play-Drucker ist, müssen wir ihn manuell installieren.
Hinweis zu Treibern:
Viele Bondrucker werden mit einem Windows-Druckertreiber ausgeliefert. Dieser dient dazu, Grafiken oder Texte, die in anderen Windows-Anwendungen gedruckt werden, in
die entsprechenden Druckerbefehle zu übersetzen.
Da wir jedoch formatierten Text und Grafiken direkt als ESC/POS-Kommandos im RAW-Format an den Drucker senden, benötigen wir keine Treiber und können den Drucker
einfach als "Generic / Text only" installieren (wenn in diesem Fall aus anderen Anwendungen heraus etwas gedruckt werden würde, würde dies nur als Text gedruckt
werden) und sind somit unabhängig von der Betriebssystem-Version und mitgelieferten Treibern.
Um den Drucker zu installieren, gehen wir in der Systemsteuerung zu "Geräte und Drucker" und klicken oben auf "Drucker hinzufügen".
Bei Windows 7 klicken wir im Dialogfeld auf "Einen lokalen Drucker hinzufügen".
Für Windows 8: Im Dialogfeld klicken wir unten auf "Der gesuchte Drucker ist nicht aufgeführt". Im nun erscheinenden Fenster wählen wir "Lokalen Drucker oder
Netzwerkdrucker mit manuellen Einstellungen hinzufügen" und klicken auf "Weiter".
Im nächsten Schritt müssen wir einen Anschluss für den Drucker auswählen. Für USB-Drucker und solche mit paralleler Schnittstelle + USB-nach-Parallel-Adapter müssen wir den Anschluss, der mit "USB" beginnt (z. B. USB001), auswählen. Wenn mehrere USB-Anschlüsse (von anderen Druckern) aufgelistet sind, müssen Sie notfalls mehrere Möglichkeiten durchprobieren, bis Sie den richtigen Anschluss gefunden haben (die Einstellung kann nacher in den Druckereigenschaften geändert werden).
Im nächsten Dialog wählen wir als Hersteller "Generic" und als Modell "Generic / Text only".
Als Name des Druckers verwenden wir "MyBonPrinter", damit wir den Drucker in VB.Net auch mit diesem Namen ansprechen können.
Um zu testen, ob der Drucker richtig installiert ist, können Sie nun eine Testseite drucken lassen. Der Drucker sollte nun einige Zeilen mit Infos über das System und den gewählten Treiber drucken. Wenn der Drucker nicht reagiert, haben Sie möglicherweise den falschen USBxxx-Port gewählt.
2. Mit VB.Net RAW-Daten an den Drucker senden
Um den Drucker nun in VB.Net verwenden zu können, müssen wir Daten im RAW-Format an den Drucker senden, d. h. wir senden direkt unsere Bytes unter Umgehung des
Druckertreibers. Da die im .Net Framework eingebauten Druckfunktionen dies nicht ermöglichen, müssen wir dafür die Windows API verwenden.
Dazu erstellen wir ein neues VB.Net-Projekt (Konsolenanwendung) mit dem Namen "EscPosDrucker" und legen die Datei RawPrinter.vb an, in die wir folgenden Inhalt einfügen:
Option Strict On Imports System Imports System.Runtime.InteropServices Imports System.IO Namespace BonPrinterUtilities ''' <summary> ''' Eine Klasse zum Kapseln eines Druckers. Mit der Methode ''' <see cref="RawPrinter.CreateDocument">CreateDocument(String)</see> ''' kann ein neues Dokument im RAW-Format erstellt werden. ''' ''' Pro RawPrinter-Instanz kann immer nur eine RawDocumentStream-Instanz ''' gleichzeitig aktiv sein. Nach der Freigabe einer RawDocumentStream- ''' Instanz kann wieder ein neues Dokument erstellt werden. ''' </summary> ''' <remarks> ''' Diese Klasse ist nicht threadsicher. Wenn verschiedene Threads drucken ''' wollen, muss für jeden Thread ein eigenens RawPrinter-Objekt ''' erstellt werden. ''' </remarks> Public Class RawPrinter Implements IDisposable ' API-Deklarationen <DllImport("winspool.Drv", EntryPoint:="OpenPrinterW", SetLastError:=True, _ CharSet:=CharSet.Unicode, ExactSpelling:=True, _ CallingConvention:=CallingConvention.Winapi)> _ Private Shared Function OpenPrinter(ByVal szPrinter As String, _ ByRef hPrinter As IntPtr, ByVal pd As IntPtr) As Boolean End Function <DllImport("winspool.Drv", EntryPoint:="ClosePrinter", SetLastError:=True, _ ExactSpelling:=True, CallingConvention:=CallingConvention.Winapi)> _ Private Shared Function ClosePrinter(ByVal hPrinter As IntPtr) As Boolean End Function <DllImport("winspool.drv", EntryPoint:="GetPrinterDriver2W", _ SetLastError:=True, CharSet:=CharSet.Unicode, ExactSpelling:=True, _ CallingConvention:=CallingConvention.Winapi)> _ Private Shared Function GetPrinterDriver2(ByVal hWnd As IntPtr, _ ByVal hPrinter As IntPtr, ByVal pEnvironment As String, _ ByVal Level As Integer, ByVal pDriverInfo As IntPtr, _ ByVal cbBuf As Integer, ByRef pcbNeeded As Integer) As Boolean End Function Private Const ERROR_INSUFFICIENT_BUFFER As Integer = 122 Private Const PRINTER_DRIVER_XPS As Integer = &H2 <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _ Private Class DRIVER_INFO_8 Public cVersion As UInteger Public pName As String Public pEnvironment As String Public pDriverPath As String Public pDataFile As String Public pConfigFile As String Public pHelpFile As String Public pDependentFiles As String Public pMonitorName As String Public pDefaultDataType As String Public pszzPreviousNames As String Public ftDriverDate As ComTypes.FILETIME Public dwlDriverVersion As ULong Public pszMfgName As String Public pszOEMUrl As String Public pszHardwareID As String Public pszProvider As String Public pszPrintProcessor As String Public pszVendorSetup As String Public pszzColorProfiles As String Public pszInfPath As String Public dwPrinterDriverAttributes As UInteger Public pszzCoreDriverDependencies As String Public ftMinInboxDriverVerDate As ComTypes.FILETIME Public dwlMinInboxDriverVerVersion As ULong End Class ' Instanz-Variablen Private ReadOnly hPrinter As IntPtr = IntPtr.Zero Private ReadOnly xpsDriver As Boolean Private currentDoc As RawDocumentStream = Nothing Private disposed As Boolean = False Private ReadOnly Property HandlePrinter() As IntPtr Get If disposed Then Throw New ObjectDisposedException("RawPrinter") Return hPrinter End Get End Property ''' <summary> ''' Erstellt eine neue Instanz. ''' </summary> ''' <param name="printerName">Der Name des zu verwendenden Druckers</param> ''' <exception cref="System.ComponentModel.Win32Exception">Beim Aufruf einer ''' Win32-API trat ein Fehler auf.</exception> Public Sub New(ByVal printerName As String) CheckApiCall(OpenPrinter(printerName, hPrinter, IntPtr.Zero)) xpsDriver = IsXpsDriver() End Sub ''' <summary> ''' Überprüft den Rückgabewert einer Win32-API-Funktion und wirft bei Bedarf ''' eine Exception. ''' </summary> ''' <param name="retVal"></param> Private Shared Sub CheckApiCall(ByVal retVal As Boolean) If Not retVal Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()) End If End Sub ''' <summary> ''' Gibt die nativen Resourcen dieses Objekts frei. ''' </summary> ''' <exception cref="System.ComponentModel.Win32Exception">Beim Aufruf einer ''' Win32-API trat ein Fehler auf.</exception> Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not disposed Then ClosePrinter(HandlePrinter) disposed = True End If End Sub Protected Overrides Sub Finalize() Try ' Im Destruktor disposen Dispose(False) Finally MyBase.Finalize() End Try End Sub Private canCreateDocument As Boolean = False ''' <summary> ''' Erstellt ein neues RawPrintDocument. ''' </summary> ''' <param name="docName">Der Name des Dokuments</param> ''' <returns>Das neue Dokument</returns> ''' <exception cref="System.ComponentModel.Win32Exception">Beim Aufruf einer ''' Win32-API trat ein Fehler auf.</exception> ''' <exception cref="InvalidOperationException">Es wurde bereits ein Dokument ''' für diesen Drucker erstellt und noch nicht freigegeben.</exception> Public Function CreateDocument(ByVal docName As String) As RawDocumentStream If disposed Then Throw New ObjectDisposedException("RawPrinter") End If If currentDoc IsNot Nothing Then Throw New InvalidOperationException("Pro RawPrinter kann immer nur " & _ "1 RawDocumentStream-Objekt erzeugt werden.") End If canCreateDocument = True Try currentDoc = New RawDocumentStream(Me, docName) Return currentDoc Finally canCreateDocument = False End Try End Function ''' <summary> ''' Überprüft, ob es sich bei dem Treiber dieses Druckers um einen ''' XPS-Treiber handelt. ''' </summary> ''' <remarks> ''' Bei Windows-Versionen kleiner als Windows 8 (NT 6.2), also ''' Windows Vista und Windows 7, wird immer ''' false zurückgegeben. ''' </remarks> ''' <returns>true, wenn es sich um einen XPS-Treiber handelt</returns> Private Function IsXpsDriver() As Boolean Dim level As Integer = 8 ' Nachschauen, wieviel Bytes als Buffer reserviert werden müssen. ' In diesem Fall muss GetPrinterDriver2 mit ERROR_INSUFFICIENT_BUFFER ' failen. Dim bytesNeeded As Integer GetPrinterDriver2(IntPtr.Zero, HandlePrinter, Nothing, level, _ IntPtr.Zero, 0, bytesNeeded) If Marshal.GetLastWin32Error() <> ERROR_INSUFFICIENT_BUFFER Then CheckApiCall(False) End If Dim driverInf As DRIVER_INFO_8 ' Nativen Speicher mit geforderter Größe erstellen Dim pBytesLength As Integer = Math.Max(bytesNeeded, _ Marshal.SizeOf(GetType(DRIVER_INFO_8))) Dim pBytes As IntPtr = Marshal.AllocHGlobal(pBytesLength) Try CheckApiCall(GetPrinterDriver2(IntPtr.Zero, HandlePrinter, Nothing, _ level, pBytes, pBytesLength, bytesNeeded)) ' Der Anfang des Bytesarrays enthält die Structure; der hintere Teil kann ' die Strings enthalten, auf die die Pointer in der Structure zeigen. ' Hier jetzt die Structure in ein verwaltetes Objekt marshallen. driverInf = DirectCast(Marshal.PtrToStructure(pBytes, _ GetType(DRIVER_INFO_8)), DRIVER_INFO_8) Finally Marshal.FreeHGlobal(pBytes) pBytes = IntPtr.Zero ' Pointer clearen End Try ' Prüfen, ob das Flag PRINTER_DRIVER_XPS gesetzt ist Return (driverInf.dwPrinterDriverAttributes And PRINTER_DRIVER_XPS) <> 0 End Function ''' <summary> ''' Ermöglicht das Senden von RAW-Dokumenten (Bytes) an den Drucker. ''' </summary> ''' <remarks> ''' Die Methoden StartPage() und EndPage() dienen zum Unterteilen des ''' Druckauftrags in Seiten (wichtig, wenn der Windows Spooler verwendet ''' wird und man Dokumente senden will, die lange zum Erstellen brauchen, ''' da dieser bis zur Fertigestellung einer Seite wartet, bevor diese ''' tatsächlich zum Drucker gesendet wird). ''' </remarks> Public Class RawDocumentStream Inherits Stream <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _ Private Class DOCINFO Public pDocName As String Public pOutputFile As String Public pDataType As String End Class ' API-Deklarationen <DllImport("winspool.Drv", EntryPoint:="StartDocPrinterW", _ SetLastError:=True, CharSet:=CharSet.Unicode, ExactSpelling:=True, _ CallingConvention:=CallingConvention.Winapi)> _ Private Shared Function StartDocPrinter(ByVal hPrinter As IntPtr, _ ByVal level As Int32, <[In](), _ MarshalAs(UnmanagedType.LPStruct)> ByVal di As DOCINFO) As Boolean End Function <DllImport("winspool.Drv", EntryPoint:="EndDocPrinter", _ SetLastError:=True, ExactSpelling:=True, _ CallingConvention:=CallingConvention.Winapi)> _ Private Shared Function EndDocPrinter(ByVal hPrinter As IntPtr) As Boolean End Function <DllImport("winspool.Drv", EntryPoint:="StartPagePrinter", _ SetLastError:=True, ExactSpelling:=True, _ CallingConvention:=CallingConvention.Winapi)> _ Private Shared Function StartPagePrinter(ByVal hPrinter As IntPtr) As Boolean End Function <DllImport("winspool.Drv", EntryPoint:="EndPagePrinter", _ SetLastError:=True, ExactSpelling:=True, _ CallingConvention:=CallingConvention.Winapi)> _ Private Shared Function EndPagePrinter(ByVal hPrinter As IntPtr) As Boolean End Function <DllImport("winspool.Drv", EntryPoint:="WritePrinter", _ SetLastError:=True, ExactSpelling:=True, _ CallingConvention:=CallingConvention.Winapi)> _ Private Shared Function WritePrinter(ByVal hPrinter As IntPtr, _ ByVal pBytes As IntPtr, ByVal dwCount As Integer, _ ByRef dwWritten As Integer) As Boolean End Function Private ReadOnly printer As RawPrinter Private pageStarted As Boolean = False Private disposed As Boolean = False Friend Sub New(ByVal printer As RawPrinter, ByVal docName As String) If Not printer.canCreateDocument Then ' Schauen, dass das über den Printer erstellt wird Throw New InvalidOperationException() End If Me.printer = printer Dim di As New DOCINFO() ' Wenn es sich um einen v4-Treiber (XPS-basiert, eingeführt mit ' Windows 8) handelt, muss "XPS_PASS" statt "RAW" verwendet werden. ' Siehe: http://support.microsoft.com/kb/2779300 di.pDataType = If(printer.xpsDriver, "XPS_PASS", "RAW") di.pDocName = docName CheckApiCall(StartDocPrinter(printer.HandlePrinter, 1, di)) End Sub ''' <summary> ''' Startet eine neue Seite (nur wichtig für den Windows Spooler). ''' </summary> ''' <exception cref="System.ComponentModel.Win32Exception">Beim ''' Aufruf einer Win32-API trat ein Fehler auf.</exception> Public Sub StartPage() If Not pageStarted Then CheckApiCall(StartPagePrinter(printer.HandlePrinter)) pageStarted = True End If End Sub ''' <summary> ''' Beendet die aktuelle Seite. ''' </summary> ''' <exception cref="System.ComponentModel.Win32Exception">Beim ''' Aufruf einer Win32-API trat ein Fehler auf.</exception> Public Sub FinishPage() If pageStarted Then CheckApiCall(EndPagePrinter(printer.HandlePrinter)) pageStarted = False End If End Sub Protected Overrides Sub Dispose(ByVal disposing As Boolean) If Not disposed Then If disposing Then If pageStarted Then FinishPage() End If CheckApiCall(EndDocPrinter(printer.HandlePrinter)) printer.currentDoc = Nothing End If disposed = True End If MyBase.Dispose(disposing) End Sub Public Overrides ReadOnly Property CanRead() As Boolean Get Return False End Get End Property Public Overrides ReadOnly Property CanSeek() As Boolean Get Return False End Get End Property Public Overrides ReadOnly Property CanWrite() As Boolean Get Return True End Get End Property Public Overrides Sub Flush() ' Ignorieren End Sub Public Overrides ReadOnly Property Length() As Long Get Throw New NotSupportedException() End Get End Property Public Overrides Property Position() As Long Get Throw New NotSupportedException() End Get Set(ByVal value As Long) Throw New NotSupportedException() End Set End Property Public Overrides Function Read(ByVal buffer As Byte(), _ ByVal offset As Integer, ByVal count As Integer) As Integer Throw New NotSupportedException() End Function Public Overrides Function Seek(ByVal offset As Long, _ ByVal origin As SeekOrigin) As Long Throw New NotSupportedException() End Function Public Overrides Sub SetLength(ByVal value As Long) Throw New NotSupportedException() End Sub ''' <summary> ''' Sendet die angegebenen Bytes an den Drucker. ''' </summary> ''' <param name="buffer"></param> ''' <param name="offset"></param> ''' <param name="count"></param> ''' <exception cref="System.ComponentModel.Win32Exception">Beim Aufruf ''' einer Win32-API trat ein Fehler auf.</exception> Public Overrides Sub Write(ByVal buffer As Byte(), _ ByVal offset As Integer, ByVal count As Integer) If disposed Then Throw New ObjectDisposedException("RawDocumentStream") End If If offset < 0 OrElse count < 0 OrElse offset + count > buffer.Length Then Throw New ArgumentException() End If If Not pageStarted Then StartPage() End If ' Pointer auf Byte-Array holen. ' In C#: fixed (byte* pBytes = buffer) { ... }, ' dann Pointerarithmetik verwenden Dim hgc As GCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned) Try Dim bytesCompleted As Integer = 0 While count - bytesCompleted > 0 Dim tempBytesWritten As Integer = 0 CheckApiCall(WritePrinter(printer.HandlePrinter, _ Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset + bytesCompleted), _ count - bytesCompleted, tempBytesWritten)) If Not (tempBytesWritten > 0) Then Exit While End If bytesCompleted += tempBytesWritten End While Finally hgc.Free() End Try End Sub End Class End Class End Namespace
Die Klasse "RawPrinter" dient dazu, einen Drucker zur Verwendung des RAW-Formats zu kapseln. Die Klasse "RawDocumentStream" ermöglicht dementsprechend das Senden von Bytes als Dokument an den Drucker.
Da normalerweise mehrere Dokumente/Bons gedruckt werden sollen, während eine Anwendung läuft, kann man beim Start der Anwendung ein RawPrinter-Objekt erstellen und dann während der Laufzeit von diesem immer wieder die CreateDocument()-Methode aufrufen, um über die RawDocumentStream-Objekte Bons zu drucken. Jedes erstellte RawDocumentStream-Objekt sollte allerdings immer sofort nach dem Senden der Bytes wieder disposed werden (mit Using ... as RawDocumentStream), damit der Druckauftrag ausgeführt werden kann.
Nun können wir in der Hauptklasse des Projektes mit folgendem Code testen, ob wir einen unformatierten Text an den Drucker senden können:
Option Strict On Imports EscPosDrucker.BonPrinterUtilities Class Program <STAThread> Shared Sub Main() ' Drucker-Objekt erstellen Using prn As New RawPrinter("MyBonPrinter") ' Neues RAW-Dokument erstellen Using doc As RawPrinter.RawDocumentStream = prn.CreateDocument("TestDoc") ' Unformatierten Text als ASCII an den Drucker senden. Dim str As String = "Hi, dies ist ein Test." & vbLf & vbLf & vbLf & vbLf & vbLf Dim buf As Byte() = System.Text.Encoding.ASCII.GetBytes(str) ' Bytes in das Dokument schreiben doc.Write(buf, 0, buf.Length) End Using End Using End Sub End Class
Der Drucker sollte nun eine Zeile mit dem oben angegebenen Text drucken:
3. Die ESC/POS-Kommandos
Um nun auch formatierten Text, Grafiken usw. drucken zu können, müssen wir spezielle Druckerbefehle verwenden. Bei den meisten Bondruckern sind dies die "ESC/POS"-Kommandos.
Das Prinzip dabei ist: Wenn normaler Text wie Buchstaben (also keine Steuerzeichen) gesendet wird, druckt der Drucker diesen ganz normal aus. Sobald aber das
Steuerzeichen ESC (ASCII-Code 0x1B) oder GS (0x1D) vorkommt, interpretiert der Drucker das/die nachfolgende(n) Byte(s) als Befehl. Auf diese Weise können
beispielsweise Kommandos wie Fettschrift, andere Schriftgröße usw. gesendet werden.
Zum Drucken von Text verwenden solche Drucker normalerweise eine Codepage, bei der jedes Zeichen aus einem Byte besteht, wie Cp437 (DOS-Codepage) oder Windows-1252. Es können somit leider nicht alle Unicode-Zeichen gedruckt werden, jedoch die meisten Zeichen, die man für normale Texte benötigt.
Eine Spezifikation der ESC/POS-Kommandos können Sie unter folgendem Link erhalten: ESC-POS-Command-Guide.pdf
(Diese Spezifikation ist allerdings von Star und schon etwas älter, sodass dort nicht alle aktuellen Kommandos enthalten sind). Eine aktuellere Version (mit BASIC-Beispielen zu den Befehlen) ist auch hier verfügbar:
Programming%20manual%20APG_1005_receipt.pdf
Um diese Kommandos auf einfache Weise in unserem Programm verwenden zu können, fügen wir die Datei "EscPosPrinter.vb" hinzu, mit folgendem Inhalt:
Option Strict On Imports System Imports System.IO Imports System.Text Imports System.Drawing Namespace BonPrinterUtilities ''' <summary> ''' Stellt einen zustandsbehafteten ESC/POS-Drucker dar. ''' </summary> ''' <remarks> ''' Diese Klasse bietet Methoden, die die ESC/POS-Befehle kapseln. ''' Zum Schreiben eines Strings können Sie die Write(String)-Methode verwenden. ''' ''' Bei jedem Aufruf einer Methode werden dabei die Kommandos in einen internen ''' Puffer geschrieben, der am Schluss mit GetCurrentPuffer() geholt werden ''' kann, um das Bytearray an den Drucker zu senden. ''' </remarks> Public Class EscPosPrinter Public Enum Alignment Left = 0 Center Right End Enum Public Class PrinterCodepage Public Shared ReadOnly _ Cp437 As New PrinterCodepage(0, Encoding.GetEncoding(437)), _ Cp852 As New PrinterCodepage(18, Encoding.GetEncoding(852)), _ Cp866 As New PrinterCodepage(17, Encoding.GetEncoding(866)), _ Cp1252 As New PrinterCodepage(16, Encoding.GetEncoding(1252)) Private ReadOnly _pageNumber As Byte Private ReadOnly _encoding As Encoding Private Sub New(ByVal pageNumber As Byte, ByVal encoding As Encoding) Me._pageNumber = pageNumber Me._encoding = encoding End Sub Public ReadOnly Property PageNumber() As Byte Get Return _pageNumber End Get End Property Public ReadOnly Property Encoding() As Encoding Get Return _encoding End Get End Property End Class ' Steuercodes für neue Zeile Private Shared ReadOnly newLineBytes As Byte() = {&HA} ' Interner Buffer Private memstr As New MemoryStream() ' Zustand des Druckers Private printerEnc As PrinterCodepage = PrinterCodepage.Cp437 Private underline As Boolean = False Private underlineDouble As Boolean = False Private emphasized As Boolean = False Private redColor As Boolean = False Private currentFontX As Integer = 0 Private currentFontY As Integer = 0 Private panelButtonsEnabled As Boolean = True Private _alignment As Alignment = Alignment.Left ''' <summary> ''' Erstellt eine neue Instanz. Dadurch wird automatisch ein ''' "Initialize Printer"-Command (ESC '@') in den Puffer geschrieben, der ''' den Drucker auf die Standardwerte zurücksetzt, damit der Zustand des ''' Druckers dem Zustand dieses Objektes entspricht. ''' </summary> Public Sub New() ' Initialize Printer Command senden Dim buf As Byte() = New Byte() {&H1B, &H40} memstr.Write(buf, 0, buf.Length) End Sub ''' <summary> ''' Schreibt den angegebenen String. ''' </summary> ''' <remarks> ''' Bei diesem Vorgang werden automatisch ''' Bytes, die in den ASCII-Steuerzeichenbereich fallen (0x00-0x1F sowie 0x7F), ''' durch Leerzeichen ersetzt. So wird verhindert, dass Strings aus fremden ''' Quellen den Zustand des Druckers verändern können. ''' ''' Dies betrifft auch Zeilenumbrüche. Um einen Zeilenumbruch zu schreiben, ''' verwenden Sie WriteLine() oder WriteLine(String). ''' ''' Beachten Sie, dass bei den meisten Bondruckern eine Zeile erst gedruckt ''' wird, sobald diese mit einem Zeilenumbruch abgeschlossen wird (auch wenn ''' die Zeile voll ist, also so viele Zeichen belegt, wie der Drucker in eine ''' Zeile drucken kann). ''' ''' Beachten Sie, dass ESC/POS-Drucker standardmäßig die DOS-Codepage 437 ''' verwenden, die u. a.Rahmenzeichen, einige griechische Zeichen und ''' mathematischen Symbole enthält. In den meisten Fällen empfiehlt sich für ''' Textdruck (v.a. europäische Texte) aber ein Umschalten auf die Windows- ''' Codepage 1252 (Methode SetCodepage(PrinterCodepage)), da hier mehr ''' unterschiedliche Buchstaben (z.B. "ß"), typographische Satzzeichen und ''' auch das €-Zeichen enthalten sind. ''' ''' Für Texte in anderen europäischen Sprachen empfehlen sich noch die ''' Codepage 852, die Zeichen für mitteleuropäische Sprachen enthält, sowie ''' die Codepage 866 mit kyrillischen Zeichen. ''' </remarks> ''' <param name="str">der String, der ausgegeben werden soll</param> Public Sub Write(ByVal str As String) Dim buf As Byte() = printerEnc.Encoding.GetBytes(str) ' Aufpassen, dass kein ASCII-Steuerzeichen vorkommt. For i As Integer = 0 To buf.Length - 1 Dim c As Byte = buf(i) If Not (c >= &H20 AndAlso c <> &H7F) Then buf(i) = CByte(AscW(" "c)) End If Next memstr.Write(buf, 0, buf.Length) End Sub ''' <summary> ''' Schreibt einen Zeilenumbruch (LF) zum Abschließen der aktuellen Zeile. ''' </summary> Public Sub WriteLine() memstr.Write(newLineBytes, 0, newLineBytes.Length) End Sub ''' <summary> ''' Schreibt den angegebenen String und danach einen Zeilenumbruch (LF). ''' Siehe Dokumentation zur Write(String)-Methode. ''' </summary> ''' <param name="str">der String, der ausgegeben werden soll</param> Public Sub WriteLine(ByVal str As String) Write(str) WriteLine() End Sub ''' <summary> ''' ESC '*' 33 nL nH d1…dk: ''' Druckt das angegebene Bild in Schwarz/Weiß. Die Höhe muss hierbei ein ''' Vielfaches von 24 sein, da das Bild wie Textzeilen ohne Zeilenabstand ''' gedruckt wird und eine Zeile (Font A) aus 24 Pixeln besteht. Die Breite ''' sollte der Druckerauflösung entsprechen (z.B. 384 Pixel beim PRP-058- ''' Drucker mit 32 Zeichen pro Zeile). ''' </summary> ''' <remarks> ''' Die Druckauflösung hängt vom Druckermodell ab und beträgt normalerweise ''' 203,2 dpi (z.B. PRP-058) oder 180 dpi. ''' </remarks> ''' <param name="bmp">Die zu druckende Bitmap</param> Public Sub PrintImage(ByVal bmp As Bitmap) If bmp.Height Mod 24 <> 0 Then Throw New ArgumentException( _ "Die Bildhöhe muss ein Vielfaches von 24 sein.") End If If bmp.Width > &H3FF Then Throw New ArgumentException( _ "Die Bildbreite darf nicht größer als 1023 sein.") End If Dim buf As Byte() Dim zeilenAnfang As Byte() = New Byte() {&H1B, &H2A, 33, _ CByte(bmp.Width And &HFF), CByte((bmp.Width >> 8) And &HFF)} Dim bildBuf As Byte() = New Byte(bmp.Width * 3 - 1) {} For i As Integer = 0 To bmp.Height \ 24 - 1 ' Durch die einzelnen Zeilen gehen buf = zeilenAnfang memstr.Write(buf, 0, buf.Length) buf = bildBuf Array.Clear(buf, 0, buf.Length) For x As Integer = 0 To bmp.Width - 1 For y As Integer = 0 To 23 Dim byteIdx As Integer = y \ 8 + x * 3 Dim c As Color = bmp.GetPixel(x, i * 24 + y) Dim bit As Boolean = c.GetBrightness() < 0.5F If bit Then buf(byteIdx) = buf(byteIdx) Or CByte(1 << (7 - (y Mod 8))) End If Next Next memstr.Write(buf, 0, buf.Length) If i <> bmp.Height \ 24 - 1 Then ' ESC J n für Paper Feed (n (hier 0) müsste eigentlich 48 sein für eine ' Zeile, aber geht mit kleineren Werten auch) ' Nicht LF verwenden, weil bei LF der normale Zeilenabstand verwendet ' wird! buf = New Byte() {&H1B, &H4A, 0} Else ' Am Schluss normaler Zeilenabstand nach unten. buf = New Byte() {&HA} End If memstr.Write(buf, 0, buf.Length) Next End Sub ''' <summary> ''' ESC '-' n: ''' Aktiviert oder deaktiviert den Underline-Modus. ''' </summary> ''' <param name="value">true, wenn der Underline-Modus aktiviert werden soll, ''' sonst false</param> ''' <param name="doubleThickness">true, wenn die Linie 2 Pixel statt 1 Pixel ''' dick sein soll</param> Public Sub SetUnderline(ByVal value As Boolean, ByVal doubleThickness As Boolean) If value <> underline OrElse (value And doubleThickness <> underlineDouble) Then underline = value underlineDouble = doubleThickness Dim buf As Byte() = New Byte() {&H1B, &H2D, _ CByte(If(value, If(doubleThickness, 2, 1), 0))} memstr.Write(buf, 0, buf.Length) End If End Sub ''' <summary> ''' ESC 'E' n: ''' Aktiviert oder deaktiviert den Fettschrift-Modus. ''' </summary> ''' <param name="value"></param> Public Sub SetEmphasized(ByVal value As Boolean) If value <> emphasized Then emphasized = value Dim buf As Byte() = New Byte() {&H1B, &H45, CByte(If(value, 1, 0))} memstr.Write(buf, 0, buf.Length) End If End Sub ''' <summary> ''' ESC 'c' '5' n: ''' Aktiviert oder deaktiviert die Panel-Buttons am Drucker ''' (z.B. den Feed-Button). ''' </summary> ''' <param name="value"></param> Public Sub SetPanelButtons(ByVal value As Boolean) If value <> panelButtonsEnabled Then panelButtonsEnabled = value Dim buf As Byte() = New Byte() {&H1B, &H63, &H35, CByte(If(value, 0, 1))} memstr.Write(buf, 0, buf.Length) End If End Sub ''' <summary> ''' ESC 'r' n: ''' Setzt die Druckfarbe auf rot oder schwarz (nur bei Modellen, ''' die dies unterstützen). ''' </summary> ''' <param name="red">true, wenn mit roter statt schwarzer Farbe gedruckt ''' werden soll</param> Public Sub SetColor(ByVal red As Boolean) If red <> redColor Then redColor = red Dim buf As Byte() = New Byte() {&H1B, &H72, CByte(If(red, 1, 0))} memstr.Write(buf, 0, buf.Length) End If End Sub ''' <summary> ''' GS '!' n: ''' Ändert die Font-Größe (Parameter jeweils von 0-7). ''' Standardwerte: x = 0, y = 0 ''' </summary> ''' <param name="x">Horizontale Größe</param> ''' <param name="y">Vertikale Größe</param> Public Sub SetFontSize(ByVal x As Integer, ByVal y As Integer) If (x < 0 OrElse x > 7) OrElse (y < 0 OrElse y > 7) Then Throw New ArgumentException("x und y müssen im Bereich 0-7 liegen.") End If If x <> currentFontX OrElse y <> currentFontY Then currentFontX = x currentFontY = y Dim buf As Byte() = New Byte() {&H1D, &H21, CByte((x << 4) Or y)} memstr.Write(buf, 0, buf.Length) End If End Sub ''' <summary> ''' ESC 't' n: ''' Schaltet auf die angegebene Codepage um. ''' </summary> Public Sub SetCodepage(ByVal codepage As PrinterCodepage) If printerEnc IsNot codepage Then printerEnc = codepage Dim buf As Byte() = New Byte() {&H1B, &H74, codepage.PageNumber} memstr.Write(buf, 0, buf.Length) End If End Sub ''' <summary> ''' ESC 'a' n: ''' Setzt die angegebene Textausrichtung. ''' </summary> ''' <param name="alignment"></param> Public Sub SetAlignment(ByVal alignment As Alignment) If _alignment <> alignment Then _alignment = alignment Dim buf As Byte() = New Byte() {&H1B, &H61, CByte(alignment)} memstr.Write(buf, 0, buf.Length) End If End Sub ''' <summary> ''' ESC 'p' m t1 t2: ''' Erzeugt einen Pulse, um eine am Drucker angeschlossene Kassenlade ''' zu öffnen. ''' </summary> ''' <param name="p5">false, wenn Pin #2 verwendet werden soll (m = 0); ''' true, wenn Pin #5 verwendet werden soll (m = 1)</param> ''' <param name="onTime">On Time: wert * 2 ms</param> ''' <param name="offTime">Off Time: wert * 2 ms</param> Public Sub SendDrawerKickoutPulse(ByVal p5 As Boolean, _ ByVal onTime As Byte, ByVal offTime As Byte) Dim buf As Byte() = New Byte() {&H1B, &H70, CByte(If(p5, 1, 0)), _ onTime, offTime} memstr.Write(buf, 0, buf.Length) End Sub ''' <summary> ''' GS 'V' m: ''' Schneidet das Papier (nur bei Modellen mit einer Auto-Cut-Funktion). ''' </summary> ''' <param name="fullCut">true, wenn ein voller Schnitt durchgeführt ''' werden soll; false, wenn ein kleines Stück freigelassen ''' werden soll</param> Public Sub CutPaper(ByVal fullCut As Boolean) Dim buf As Byte() = New Byte() {&H1D, &H56, CByte(If(fullCut, 0, 1))} memstr.Write(buf, 0, buf.Length) End Sub ''' <summary> ''' GS 'h' n: ''' Legt die Höhe von 2D-Barcodes (z.B. EAN-13 und EAN-8) fest. ''' Der Standardwert ist 162. ''' </summary> ''' <param name="height">die Höhe der Barcodes (zwischen 1 und 255)</param> Public Sub SetBarcodeHeight(ByVal height As Byte) If height = 0 Then Throw New ArgumentException("height darf nicht 0 sein.") Dim buf As Byte() = New Byte() {&H1D, &H68, height} memstr.Write(buf, 0, buf.Length) End Sub ''' <summary> ''' GS 'k' 67/68 12/7 d1…dk: ''' Druckt einen EAN-13- oder EAN-8-Barcode. ''' </summary> ''' <param name="ean">Bei EAN-13: 12- oder 13-stelliger Barcode; ''' bei EAN-8: 7- oder 8-stelliger Barcode. ''' Bei 8 bzw. 13 Stellen wird die Prüfziffer ignoriert; ''' sie wird in allen Fällen vom Drucker berechnet.</param> Public Sub PrintEanBarcode(ByVal ean As String) If Not (ean.Length = 7 OrElse ean.Length = 8 OrElse _ ean.Length = 12 OrElse ean.Length = 13) Then Throw New ArgumentException("Ein EAN-13-Barcode muss aus 12 oder 13 " & _ "Zeichen bestehen; ein EAN-8-Barcode aus 7 oder 8 Zeichen.") End If Dim ean8 As Boolean = ean.Length = 7 OrElse ean.Length = 8 For i As Integer = 0 To ean.Length - 1 If ean(i) < "0"c OrElse ean(i) > "9"c Then Throw New ArgumentException("Es dürfen nur Ziffern von " & _ "0-9 verwendet werden.") End If Next Dim buf As Byte() = New Byte(If(ean8, 11, 16) - 1) {} buf(0) = &H1D buf(1) = &H6B buf(2) = CByte(If(ean8, 68, 67)) buf(3) = CByte(If(ean8, 7, 12)) For i As Integer = 0 To (If(ean8, 7, 12)) - 1 buf(4 + i) = CByte(AscW(ean(i))) Next memstr.Write(buf, 0, buf.Length) End Sub ''' <summary> ''' Gibt den aktuellen Pufferinhalt zurück und leert diesen anschließend. ''' </summary> ''' <returns></returns> Public Function GetCurrentBuffer() As Byte() Dim buf As Byte() = memstr.ToArray() memstr.Close() memstr = New MemoryStream() Return buf End Function End Class End Namespace
Diese Klasse "EscPosPrinter" bietet Methoden zum Senden einiger ESC/POS-Kommandos. Falls Sie noch andere Kommandos verwenden möchten, die in der Spezifikation angegeben sind, können Sie diese in der obigen Klasse implementieren.
Um einen String zu schreiben, benutzen Sie die Methode Write(String) oder WriteLine(String). Beachten Sie, dass bei diesen Methoden Bytes, die in den Steuerzeichenbereich fallen (0x00-0x19 sowie 0x7F), automatisch durch Leerzeichen ersetzt werden, um zu verhinden, dass Strings aus fremden Quellen durch Senden von Kommandos den Zustand des Druckers manipulieren können. Dies betrifft auch Zeilenumbrüche. Um einen Zeilenumbruch zu senden, benutzen Sie die Methode WriteLine().
Die Klasse bestitzt einen internen Puffer (MemoryStream), in den alle Bytes geschrieben werden. Am Ende kann man dann den aktuellen Puffer mit GetCurrentBuffer() als Byte-Array holen und per RawDocumentStream (aus dem vorherigen Abschnitt) an den Drucker senden. Die XML-Dokumentation der EscPosPrinter-Klasse enthält Informationen über alle implementierten Methoden und über die Verwendung von verschiedenen Codepages zum Drucken von Text.
So können wir nun auf folgende Weise formatierten Text zum Drucker schicken:
' EscPosPrinter-Objekt erstellen Dim p As New EscPosPrinter() ' Auf Windows-1252-Codepage umschalten p.SetCodepage(EscPosPrinter.PrinterCodepage.Cp1252) p.WriteLine("Hi, wie geht’s?") p.SetFontSize(1, 0) ' Doppelte Schriftbreite setzen p.WriteLine("Hi, wie geht’s?") p.SetFontSize(0, 0) ' Einstellung rückgängig ' Linefeed, damit die unterste Zeile sichtbar wird p.WriteLine() p.WriteLine() p.WriteLine() p.WriteLine() Dim bytes As Byte() = p.GetCurrentBuffer() ' Bytes abholen ' Drucker-Objekt erstellen Using prn As New RawPrinter("MyBonPrinter") ' Neues RAW-Dokument erstellen Using doc As RawPrinter.RawDocumentStream = prn.CreateDocument("TestDoc") ' Bytes an den Drucker senden. doc.Write(bytes, 0, bytes.Length) End Using End Using
Ergebnis:
4. Verschiedene Beispiele zur Verwendung der ESC/POS-Kommandos
Nun sehen wir uns einige Beispiele für die Möglichkeiten an, die uns die ESC/POS-Kommandos bieten.
4.1. Formatierter Text (Fettschrift, Unterstrichen, unterschiedliche Fontsize, Textausrichtung und Umschalten der Codepages)
Im folgenden Beispiel (Methode Example1()) werden einige Formatierungsfunktionen angewendet sowie ein Text jeweils mit der Codepage 1252 und mit der Codepage 437 gedruckt.
Da für jedes Beispiel der Programmablauf der gleiche ist (EscPosObjekt erstellen, Bytes holen, RawDocumentStream erstellen und Bytes senden), erstellen wir uns eine Methode "RunPrintMethod", die diese Aufgaben erledigt und als Parameter einen Delegaten auf eine Methode erhält, die den eigentlichen Beispielcode ausführt. Diese können wir dann einfach aus der Main-Methode aufrufen:
Option Strict On Imports System.IO Imports System.Drawing Imports EscPosDrucker.BonPrinterUtilities Public Class Program <STAThread> Shared Sub Main() ' Example1 aufrufen RunPrintMethod(AddressOf Example1) End Sub Private Shared Sub RunPrintMethod(method As Action(Of EscPosPrinter)) ' Führt die angegebene Methode aus und sendet das Ergebnis an den ' Bondrucker mit Namen "MyBonPrinter". Dim printer As New EscPosPrinter() ' Encoding 1252 sollte für normale Texte verwendet werden. printer.SetCodepage(EscPosPrinter.PrinterCodepage.Cp1252) ' Methode mit Beispielcode ausführen method(printer) ' Line Feed, damit unterste Zeile sichtbar wird. printer.WriteLine() printer.WriteLine() printer.WriteLine() printer.WriteLine() ' Jetzt das Ergebnis-Bytearray an den Drucker als 1 Dokument senden. Dim bytes As Byte() = printer.GetCurrentBuffer() Using p As New RawPrinter("MyBonPrinter") Using doc As RawPrinter.RawDocumentStream = p.CreateDocument("TestDoc") doc.Write(bytes, 0, bytes.Length) End Using End Using End Sub Private Shared Sub Example1(p As EscPosPrinter) p.WriteLine("Hi! Höhö. ÄÖÜäöü @ Servus") ' Fett, Unterstrichen p.SetEmphasized(True) p.Write("Fett ") p.SetUnderline(True, True) p.Write("F & U ") p.SetEmphasized(False) p.Write(" Underline") p.SetUnderline(False, False) p.WriteLine() 'Zeilenumbruch ' Mittig, doppelte Breite und Höhe p.SetAlignment(EscPosPrinter.Alignment.Center) p.Write("Test: ") p.SetFontSize(1, 0) p.WriteLine("Hehe") p.SetFontSize(0, 1) p.WriteLine("Hehe") ' Einstellungen rückgängig machen p.SetFontSize(0, 0) p.SetAlignment(EscPosPrinter.Alignment.Left) p.WriteLine() ' Leerzeile ' Rahmenzeichen U+2550 drucken. Dafür auf Cp 437 umschalten. p.SetCodepage(EscPosPrinter.PrinterCodepage.Cp437) p.WriteLine(New String(ChrW(&H2550), 32)) p.SetCodepage(EscPosPrinter.PrinterCodepage.Cp1252) ' wieder auf Cp1252 umschalten p.WriteLine() ' Beispiel für unterschiedliche Ergebnisse mit Cp437 und Cp1252. p.WriteLine("Codepage 1252/437:") Dim text As String = "•äß× ŠØÐ ±v8¶ aßGpSe „Hi" & ChrW(&H201C) & " 4€ …" p.WriteLine(text) ' Cp1252 p.SetCodepage(EscPosPrinter.PrinterCodepage.Cp437) p.WriteLine(text) ' Cp437 p.SetCodepage(EscPosPrinter.PrinterCodepage.Cp1252) End Sub End Class
Ergebnis:
4.2. Drucken von Grafiken
Mit den ESC/POS-Kommandos können wir auch Grafiken drucken. Allerdings werden diese nur schwarz/weiß gedruckt.
Die Höhe der zu druckenden Grafik sollte ein Vielfaches von 24 sein, da diese zeilenweise gedruckt wird und eine Zeile im Font A 24 Pixel hoch ist.
Da der Drucker PRP-058 eine horizontale Auflösung von 384 Pixeln hat, erstellen wir ein Bitmap mit ebendieser Breite und Höhe und können dort beispielsweise die
aktuelle Uhrzeit als Analoguhr-Grafik drucken.
Dazu fügen wir folgende zwei Methoden hinzu:
Private Shared Sub Example2(printer As EscPosPrinter) ' Bild mit Uhrzeit erstellen Dim zeitNow As DateTimeOffset = DateTimeOffset.Now Using bmp As Bitmap = GenerateClockImage(zeitNow.TimeOfDay, 384, 384) printer.WriteLine("Aktuelle Uhrzeit:") printer.WriteLine() printer.PrintImage(bmp) printer.WriteLine(zeitNow.ToString()) End Using End Sub Private Shared Function GenerateClockImage(zeit As TimeSpan, _ width As Integer, height As Integer) As Bitmap Dim bmp As New Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format24bppRgb) Using g As Graphics = Graphics.FromImage(bmp) g.Clear(Color.White) Using p As New Pen(Color.Black, 2.0F) g.DrawArc(p, New Rectangle(0, 0, bmp.Width - 1, bmp.Height - 1), 0, 360) End Using ' Striche malen Using p As New Pen(Color.Black, 9.0F) For i As Integer = 0 To 12 - 1 Dim winkel As Double = i / 12.0R * 2.0R * Math.PI g.DrawLine(p, New PointF(CSng(bmp.Width / 2 + 0.45 * bmp.Width * Math.Cos(winkel)), _ CSng(bmp.Height / 2 - 0.45 * bmp.Height * Math.Sin(winkel))), _ New PointF(CSng(bmp.Width / 2 + 0.33 * bmp.Width * Math.Cos(winkel)), _ CSng(bmp.Height / 2 - 0.33 * bmp.Height * Math.Sin(winkel)))) Next End Using ' Stundenzeiger malen Using p As New Pen(Color.Black, 6.0F) Dim winkel = (0.25R + -zeit.TotalMilliseconds / _ (1000.0R * 60.0R * 60.0R * 12.0R)) * 2.0R * Math.PI g.DrawLine(p, New PointF(CSng(bmp.Width / 2 + 80 * Math.Cos(winkel)), _ CSng(bmp.Height / 2 - 80 * Math.Sin(winkel))), _ New PointF(CSng(bmp.Width / 2 - 15 * Math.Cos(winkel)), _ CSng(bmp.Height / 2 + 15 * Math.Sin(winkel)))) End Using ' Minutenzeiger malen Using p As New Pen(Color.Black, 3.0F) Dim winkel = (0.25R + -zeit.TotalMilliseconds / _ (1000.0R * 60.0R * 60.0R)) * 2.0R * Math.PI g.DrawLine(p, New PointF(CSng(bmp.Width / 2 + 105 * Math.Cos(winkel)), _ CSng(bmp.Height / 2 - 105 * Math.Sin(winkel))), _ New PointF(CSng(bmp.Width / 2 - 20 * Math.Cos(winkel)), _ CSng(bmp.Height / 2 + 20 * Math.Sin(winkel)))) End Using ' Sekundenzeiger malen Using p As New Pen(Color.Black, 1.0F) Dim winkel = (0.25R + -zeit.TotalMilliseconds / _ (1000.0R * 60.0R)) * 2.0R * Math.PI g.DrawLine(p, New PointF(CSng(bmp.Width / 2 + 105 * Math.Cos(winkel)), _ CSng(bmp.Height / 2 - 105 * Math.Sin(winkel))), _ New PointF(CSng(bmp.Width / 2 - 25 * Math.Cos(winkel)), _ CSng(bmp.Height / 2 + 25 * Math.Sin(winkel)))) End Using End Using Return bmp End Function
Anschließend können wir das Beispiel durch:
RunPrintMethod(AddressOf Example2)
laufen lassen.
Ergebnis:
4.3. Drucken von EAN-13- und EAN-8-Barcodes
ESC/POS enthält auch Kommandos zum Drucken von Barcodes. In der Klasse EscPosPrinter sind deshalb Methoden zum Drucken von EAN-8- und EAN-13-Barcodes integriert.
Hinzu testen wir folgendes Beispiel zum Drucken eines EAN-13-Barcodes:
Private Shared Sub Example3(p As EscPosPrinter) ' EAN-13-Barcode zeichnen Dim ean13 As String = "2912345678906" p.SetAlignment(EscPosPrinter.Alignment.Center) p.WriteLine("EAN-13:") p.WriteLine() p.SetBarcodeHeight(120) p.PrintEanBarcode(ean13) p.WriteLine(ean13) p.SetAlignment(EscPosPrinter.Alignment.Left) End Sub
Aufruf dann mit:
RunPrintMethod(AddressOf Example3)
Ergebnis:
4.4. Drucken von QR-Codes oder anderen 2D-Codes
Zwar enthalten die ESC/POS-Kommandos nur Befehle zum Drucken von 1D-Barcodes. Jedoch können wir beispielsweise unter Zuhilfenahme der Library "MessagingToolkit QRCode" (verfügbar unter:
http://platform.twit88.com/projects/mt-qrcode zur Laufzeit einen QR-Code als Bitmap erstellen lassen und diesen dann, ähnlich wie oben mit der
Uhrzeit-Grafik, über die PrintImage-Methode drucken lassen.
Hierzu können wir folgendes Beispiel verwenden (es muss ein Verweis auf die QRCode-Library hinzugefügt werden), mit dem zur Laufzeit ein QR-Code mit der aktuellen Uhrzeit erstellt wird:
Private Shared Sub Example4(p As EscPosPrinter) Dim str As String = "Zeit: " + DateTimeOffset.Now.ToString() ' QR-Code zur Laufzeit erstellen, ' der die aktuelle Uhrzeit als Text enthält Dim qrCodeEncoder As New MessagingToolkit.QRCode.Codec.QRCodeEncoder() qrCodeEncoder.CharacterSet = "UTF-8" qrCodeEncoder.QRCodeEncodeMode = _ MessagingToolkit.QRCode.Codec.QRCodeEncoder.ENCODE_MODE.BYTE qrCodeEncoder.QRCodeScale = 6 qrCodeEncoder.QRCodeVersion = 0 qrCodeEncoder.QRCodeErrorCorrect = _ MessagingToolkit.QRCode.Codec.QRCodeEncoder.ERROR_CORRECTION.M Using image As Bitmap = qrCodeEncoder.Encode(str) Using bmp As Bitmap = New Bitmap(image.Width, _ ((image.Height + 23) \ 24) * 24, _ System.Drawing.Imaging.PixelFormat.Format24bppRgb) Using g As Graphics = Graphics.FromImage(bmp) g.Clear(Color.White) g.DrawImage(image, New Point(0, 0)) End Using p.SetAlignment(EscPosPrinter.Alignment.Center) p.PrintImage(bmp) End Using End Using p.WriteLine(str) p.SetAlignment(EscPosPrinter.Alignment.Left) End Sub
Aufruf mit:
RunPrintMethod(AddressOf Example4)
Ergebnis:
Falls Sie einen Bondrucker mit Autcut-Funktion verwenden, können Sie durch Aufruf der Methode CutPaper(Boolean) einen Schnitt an der aktuellen Stelle auslösen (wurde nicht getestet). Eine an den Drucker angeschlossene Kassenlade können Sie mit der Methode SendDrawerKickoutPulse(Boolean, Byte, Byte) öffnen lassen (ebenfalls nicht getestet).
Viel Spaß!