vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#
NEU! sevCoolbar 3.0 - Professionelle Toolbars im modernen Design!  
 vb@rchiv Quick-Search: Suche startenErweiterte Suche starten   Impressum  | Datenschutz  | vb@rchiv CD Vol.6  | Shop Copyright ©2000-2024
 
zurück
Rubrik: Drucker   |   VB-Versionen: VB2008, VB2010, VB201208.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ßerBewertung:     [ Jetzt bewerten ]Views:  34.332 
www.preisser-it.deSystem:  WinXP, Win7, Win8, Win10, Win11kein 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ß!

Dieser Tipp wurde bereits 34.332 mal aufgerufen.

Voriger Tipp   |   Zufälliger Tipp   |   Nächster Tipp

Über diesen Tipp im Forum diskutieren
Haben Sie Fragen oder Anregungen zu diesem Tipp, können Sie gerne mit anderen darüber in unserem Forum diskutieren.

Aktuelle Diskussion anzeigen (2 Beiträge)

nach obenzurück


Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Tipps & Tricks finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6

Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv!
- nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten
- Symbol-Galerie mit mehr als 3.200 Icons im modernen Look
Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m.
 
   

Druckansicht Druckansicht Copyright ©2000-2024 vb@rchiv Dieter Otter
Alle Rechte vorbehalten.
Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein.

Diese Seiten wurden optimiert für eine Bildschirmauflösung von mind. 1280x1024 Pixel