如何使用 PrintDocument 在热敏打印机上打印文本文件?

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

我正在使用 C# 和 Winforms 创建一个应用程序,现在我需要在热敏打印机上打印销售收据。为此,我正在创建一个文本文件并使用 PrintDocument 读取它以进行打印,但我无法执行此操作,因为我不知道如何配置纸张尺寸、在纸张上对齐文本中心以及其他配置。当我打印时,文本文件打印出来了,但是很乱,打印结束后纸张就不会停止。

我该怎么做?

正在尝试。

private PrintDocument printDocument = new PrintDocument();
private static String RECEIPT = Environment.CurrentDirectory + @"\comprovantes\comprovante.txt";
private String stringToPrint = "";

private void button1_Click(object sender, EventArgs e)
{
    generateReceipt();
    printReceipt();
}

private void generateReceipt()
{
    FileStream fs = new FileStream(COMPROVANTE, FileMode.Create);
    StreamWriter writer = new StreamWriter(fs);
    writer.WriteLine("==========================================");
    writer.WriteLine("          NOME DA EMPRESA AQUI            ");
    writer.WriteLine("==========================================");
    writer.Close();
    fs.Close();
}

private void printReceipt()
{
    FileStream fs = new FileStream(COMPROVANTE, FileMode.Open);
    StreamReader sr = new StreamReader(fs);
    stringToPrint = sr.ReadToEnd();
    printDocument.PrinterSettings.PrinterName = DefaultPrinter.GetDefaultPrinterName();
    printDocument.PrintPage += new PrintPageEventHandler(printPage);
    printDocument.Print();
    sr.Close();
    fs.Close();
}

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

    // Sets the value of charactersOnPage to the number of characters 
    // of stringToPrint that will fit within the bounds of the page.
    graphics.MeasureString(stringToPrint, this.Font,
        e.MarginBounds.Size, StringFormat.GenericTypographic,
        out charactersOnPage, out linesPerPage);

    // Draws the string within the bounds of the page
    graphics.DrawString(stringToPrint, this.Font, Brushes.Black,
        e.MarginBounds, StringFormat.GenericTypographic);

    // Remove the portion of the string that has been printed.
    stringToPrint = stringToPrint.Substring(charactersOnPage);

    // Check to see if more pages are to be printed.
    e.HasMorePages = (stringToPrint.Length > 0);
}

我正在尝试创建这种收据模型。

使用RDLC,但是启动打印速度很慢。我关注这个example

//works but slow
private void Run() {
    LocalReport report = new LocalReport();
    report.ReportPath = @"..\..\reports\ReciboDeVenda.rdlc";
    Export(report);
    Print();
}

private void Export(LocalReport report) {
    string deviceInfo =
      @"<DeviceInfo>
        <OutputFormat>EMF</OutputFormat>
        <PageWidth>8.5in</PageWidth>
        <PageHeight>11in</PageHeight>
        <MarginTop>0.25in</MarginTop>
        <MarginLeft>0.25in</MarginLeft>
        <MarginRight>0.25in</MarginRight>
        <MarginBottom>0.25in</MarginBottom>
    </DeviceInfo>";
    Warning[] warnings;
    m_streams = new List<Stream>();
    report.Render("Image", deviceInfo, CreateStream,
       out warnings);
    foreach (Stream stream in m_streams)
        stream.Position = 0;
}

private Stream CreateStream(string name, string fileNameExtension, 
                            Encoding encoding, string mimeType, bool willSeek) {
        Stream stream = new MemoryStream();
        m_streams.Add(stream);
        return stream;
}

private void Print() {
    if (m_streams == null || m_streams.Count == 0)
        throw new Exception("Error: no stream to print.");
    PrintDocument printDoc = new PrintDocument();
    printDoc.PrinterSettings.PrinterName = DefaultPrinter.GetDefaultPrinterName();
    if (!printDoc.PrinterSettings.IsValid) {
        throw new Exception("Error: cannot find the default printer.");
    }else {
        printDoc.PrintPage += new PrintPageEventHandler(PrintPage);
        m_currentPageIndex = 0;
        printDoc.Print();
    }
}

private void PrintPage(object sender, PrintPageEventArgs ev) {
    Metafile pageImage = new Metafile(m_streams[m_currentPageIndex]);

    // Adjust rectangular area with printer margins.
    Rectangle adjustedRect = new Rectangle(
        ev.PageBounds.Left - (int)ev.PageSettings.HardMarginX,
        ev.PageBounds.Top - (int)ev.PageSettings.HardMarginY,
        ev.PageBounds.Width,
        ev.PageBounds.Height);

    // Draw a white background for the report
    ev.Graphics.FillRectangle(Brushes.White, adjustedRect);

    // Draw the report content
    ev.Graphics.DrawImage(pageImage, adjustedRect);

    // Prepare for the next page. Make sure we haven't hit the end.
    m_currentPageIndex++;
    ev.HasMorePages = (m_currentPageIndex < m_streams.Count);
}

您的热敏打印机支持 OPOS 吗?

如果是,我建议您尝试使用 Microsoft Point of Service

如果您将纯字符串发送到您的 printPage(object sender, PrintEventArgs e) 方法,它只会以看起来 'messy'(如您所命名)的所有相同字体打印纯文本 如果你想用不同的字体(粗体,常规)打印出来,你必须手动完成:

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 );
}

对我的示例的可能改进:

  • 使用 graphics.MeasureString().
  • 可以更好地使 header 字符串居中
  • 项目列表最好是企业列表class而不是纯字符串

因为这一切都是很多工作,你真的应该考虑使用 RDLC 或一些第三方软件来设计你的文档。

如果我想自己创建报告,而不是尝试创建文本文件,我将使用 HTML 进行渲染。 另外为了使渲染逻辑和混合 html 标签和数据更简单,我会使用 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 方法。

下载

您可以从 r-aghaei/HtmlUsingRuntimeT4.

克隆或下载工作示例

为什么HTML?

因为格式简单灵活。您可以使用 html 标签和 css 样式的所有功能来格式化文本。这真的比使用 DrawString.

更好

为什么 运行 时间文本模板?

因为它使混合数据和 html 变得非常容易,您可以使用 C# 语言执行一些任务,例如计算总和、迭代模型记录等。它使用 T4 模板引擎并允许您在 运行 时使用模板并将数据提供给模板。

例子

1- 将模型添加到项目中:

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- 添加一个运行-time Text Template(也称为Preprocessed Text Template)到项目中并命名为ReportTemplate.tt。打开它并添加这样的代码:

<#@ 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- 在窗体上放置一个 WebBrowser 控件并在要生成报告的地方编写这样的代码:

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-如需打印报告,可拨打:

this.webBrowser1.Print();

您应该在文档完成后调用 Print。所以如果你想直接打印而不向用户显示输出,你可以处理 DocumentCompleted 事件并在那里调用 Print

结果如下: