How to print a text file on thermal printer using PrintDocument?

If I want to create a report myself, instead of trying to create a text file, I'll use HTML for rendering. Also to make rendering logic and mixing html tags and data simpler, I'd use T4 Run-time Text Templates. Then I can pass data to the html template and render the report simply. Then it's enough to assign the output string to a DocumentText property of a WebBrowser and call its Print method.

Download

You can clone or download a working example from r-aghaei/HtmlUsingRuntimeT4.

Why HTML?

Because of simple and flexible formatting. You can use all power of html tags and css styles to format the text. It's really better than using DrawString.

Why Run-time Text Templates?

Because it makes mixing data and html really easy and you can use C# language to perform some tasks like calculating sum, iterate over model records and so on. It uses T4 templating engine and lets you to use the template at run-time and feed data to the template.

Example

1- Add a model to project:

using System;
using System.Collections.Generic;

namespace Sample
{
    public class ReportModel
    {
        public string CustomerName { get; set; }
        public DateTime Date { get; set; }
        public List<OrderItem> OrderItems { get; set; }
    }
    public class OrderItem
    {
        public string Name { get; set; }
        public int Price { get; set; }
        public int Count { get; set; }
    }
}

2- Add a Run-time Text Template (also known as Preprocessed Text Template) to the project and name it ReportTemplate.tt. Open it and add such code:

<#@ template language="C#"#>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ parameter name="Model" type="Sample.ReportModel"#>
<html>
<head>
    <title></title>
    <style type="text/css">
        body { font-family: Calibri;width:400px;}
        table { text-align:center; }
        .container {width:400px; height:100%;}
    </style>
</head>
<body>
<div class="container">
<h1 style="text-align:center;">Order</h1>
<hr/>
<table style="width:100%">
    <tr>
        <td>Customer: <#=Model.CustomerName#></td>
        <td>Order Date: <#=Model.Date#></td>
    </tr>
</table>
<hr/>
<table style="width:100%">
    <tr><th>Index</th><th>Name</th><th>Price</th><th>Count</th><th>Sum</th></tr>
    <#
    int index =1;
    foreach(var item in Model.OrderItems) 
    {
    #>
    <tr>
        <td><#=index#></td>
        <td><#=item.Name#></td>
        <td><#=item.Price#></td>
        <td><#=item.Count#></td>
        <td><#=item.Count * item.Price#></td>
    </tr>
    <#
        index++;
    }
    var total= Model.OrderItems.Sum(x=>x.Count * x.Price);
    #>
    <tr><td></td><td></td><td></td><th>Total:</th><th><#=total#></th></tr>
</table>
<div>
</body>
</html>

3- Put a WebBrowser control on the form and write such code where you want to generate the report:

var rpt = new ReportTemplate();
rpt.Session = new Dictionary<string, object>();
rpt.Session["Model"] = new Sample.ReportModel
{
    CustomerName = "Reza",
    Date = DateTime.Now,
    OrderItems = new List<Sample.OrderItem>()
    {
        new Sample.OrderItem(){Name="Product 1", Price =100, Count=2 },
        new Sample.OrderItem(){Name="Product 2", Price =200, Count=3 },
        new Sample.OrderItem(){Name="Product 3", Price =50, Count=1 },
    }
};
rpt.Initialize();
this.webBrowser1.DocumentText= rpt.TransformText();

4- If you want to print the report, you can call:

this.webBrowser1.Print();

You should call Print after the document completed. So if you want to print directly without showing output to the user, you can handle DocumentCompleted event and call Print there.

Here is the result:

enter image description here


If you send a plain string to your printPage(object sender, PrintEventArgs e) method, it will just print plain text in all the same font which looks 'messy' (as you named it) If you want it to be printed well formatted with different fonts (bold, regular), you have to do it all manually:

List<string> itemList = new List<string>()
{
    "201", //fill from somewhere in your code
    "202"
};

private void printPage( object sender, PrintPageEventArgs e )
{
    Graphics graphics = e.Graphics;

    Font regular = new Font( FontFamily.GenericSansSerif, 10.0f, FontStyle.Regular );
    Font bold = new Font( FontFamily.GenericSansSerif, 10.0f, FontStyle.Bold );

    //print header
    graphics.DrawString( "FERREIRA MATERIALS PARA CONSTRUCAO LTDA", bold, Brushes.Black, 20, 10 );
    graphics.DrawString( "EST ENGENHEIRO MARCILAC, 116, SAO PAOLO - SP", regular, Brushes.Black, 30, 30 );
    graphics.DrawString( "Telefone: (11)5921-3826", regular, Brushes.Black, 110, 50 );
    graphics.DrawLine( Pens.Black, 80, 70, 320, 70 );
    graphics.DrawString( "CUPOM NAO FISCAL", bold, Brushes.Black, 110, 80 );
    graphics.DrawLine( Pens.Black, 80, 100, 320, 100 );

    //print items
    graphics.DrawString( "COD | DESCRICAO                      | QTY | X | Vir Unit | Vir Total |", bold, Brushes.Black, 10, 120 );
    graphics.DrawLine( Pens.Black, 10, 140, 430, 140 );

    for( int i = 0; i < itemList.Count; i++ )
    {
        graphics.DrawString( itemList[i].ToString(), regular, Brushes.Black, 20, 150 + i * 20 );
    }

    //print footer
    //...

    regular.Dispose();
    bold.Dispose();

    // Check to see if more pages are to be printed.
    e.HasMorePages = ( itemList.Count > 20 );
}

Possible improvements on my example:

  • Centering the header strings could better be done using graphics.MeasureString().
  • List of items should better be a list of a business class insted of a plain string

Because this all is a lot of work, you should really consider using RDLC or some third party software to design your documents.


Does your Thermal Printer support OPOS?

If it does, I recommend that you try using Microsoft Point of Service