网上也查过很多关于打印小票的资料,终究没有逃掉要用 Windows API .其实我不太喜欢用这个东东,主要是不好理解,尤其是参数。但即然.NET没有提供有效的调用LPT的方法,那就只能用了。
当前,有些热敏打印机不是 LPT 端口的,这种情况不适用下面的类,请注意!
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
using Microsoft.Win32.SafeHandles;
namespace WaterClassLibrary
{
/// <summary>
/// LPT端口操作类,请使用 Open() 打开端口,最后结束时调用 Close()
/// 本类调用了 Windows API
/// </summary>
public class Cls_PrintLPT
{
#region 申明使用 Windows API
static class DESIREDACCESS
{
public const uint GENERIC_READ = 0x80000000;
public const uint GENERIC_WRITE = 0x40000000;
public const uint GENERIC_EXECUTE = 0x20000000;
public const uint GENERIC_ALL = 0x10000000;
}
/// <summary>
/// Sharing mode of the file or object
///</summary>
static class SHAREMODE
{
public const uint FILE_SHARE_READ = 0x00000001;
public const uint FILE_SHARE_WRITE = 0x00000002;
public const uint FILE_SHARE_DELETE = 0x00000004;
}
/// <summary>
/// Action to take on files that exist, and which action to take when files do not exist.
/// </summary>
static class CREATIONDISPOSITION
{
public const uint CREATE_NEW = 1;
public const uint CREATE_ALWAYS = 2;
public const uint OPEN_EXISTING = 3;
public const uint OPEN_ALWAYS = 4;
public const uint TRUNCATE_EXISTING = 5;
}
/// <summary>
/// File attributes and flags for the file.
/// </summary>
static class FLAGSANDATTRIBUTES
{
public const uint FILE_FLAG_WRITE_THROUGH = 0x80000000;
public const uint FILE_FLAG_OVERLAPPED = 0x40000000;
public const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
public const uint FILE_FLAG_RANDOM_ACCESS = 0x10000000;
public const uint FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000;
public const uint FILE_FLAG_DELETE_ON_CLOSE = 0x04000000;
public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
public const uint FILE_FLAG_POSIX_SEMANTICS = 0x01000000;
public const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
public const uint FILE_FLAG_OPEN_NO_RECALL = 0x00100000;
public const uint FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000;
}
/// <summary>
/// 申请打开文件函数
/// 使用本函数将对应使用 FileStream及StreamWriter 进行端口指令发送
/// 当 FileStream及StreamWriter 都 Close 后,本函数打开的端口也会
/// 被关闭,所以不需要单独再申明使用 CloseHandle(IntPtr hObject)
/// </summary>
/// <param name="lpFileName"></param>
/// <param name="dwDesiredAccess"></param>
/// <param name="dwShareMode"></param>
/// <param name="lpSecurityAttributes"></param>
/// <param name="dwCreationDisposition"></param>
/// <param name="dwFlagsAndAttributes"></param>
/// <param name="hTemplateFile"></param>
/// <returns></returns>
[DllImport("kernel32.dll", EntryPoint = "CreateFile", CharSet = CharSet.Auto)]
private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, int lpSecurityAttributes
, int dwCreationDisposition, int dwFlagsAndAttributes, int hTemplateFile);
#endregion
/// <summary>
/// 打开端口的指针
/// </summary>
SafeFileHandle iHandle;
/// <summary>
/// LPT端口号
/// </summary>
private string LPT_Name;
/// <summary>
/// 操作端口的文件流
/// </summary>
FileStream fs;
/// <summary>
/// 操作端口文件流的写入操作流
/// </summary>
StreamWriter sw;
/// <summary>
/// 常用 ESC 命令
/// </summary>
static class ESC_CMD
{
/// <summary>
/// 立即打印并换行,本模式下不关闭文件流不起作用
/// </summary>
public static string PrintAndLineFeed = ((char)10).ToString();
/// <summary>
/// 初始化打印机,使得打印设置恢复默认
/// </summary>
public static string IniPrinter = ((char)(27)).ToString() + ((char)(64)).ToString();
/// <summary>
/// 设置行高为30
/// </summary>
public static string LineHeight = ((char)(27)).ToString() + ((char)(51)).ToString() + ((char)(30)).ToString();
/// <summary>
/// 开始加粗字体
/// </summary>
public static string StrWiderStart = ((char)(27)).ToString() + ((char)(33)).ToString() + ((char)(48)).ToString();//加粗
/// <summary>
/// 取消加宽加粗打印
/// </summary>
public static string StrWiderClear = ((char)(27)).ToString() + ((char)(33)).ToString() + ((char)(0)).ToString();//取消加宽加粗打印
/// <summary>
/// 居中命令
/// </summary>
public static string StrMiddleStart = ((char)27).ToString() + ((char)97).ToString() + ((char)1).ToString();
/// <summary>
/// 取消居中
/// </summary>
public static string StrMiddelClear = ((char)27).ToString() + ((char)97).ToString() + ((char)0).ToString();
}
/// <summary>
/// LPT端口号,例:LPT1
/// </summary>
/// <param name="lpt_name"></param>
public Cls_PrintLPT(string lpt_name)
{
LPT_Name = lpt_name;
}
/// <summary>
/// 实例化空类,以便于使用其它的字符串方法
/// </summary>
public Cls_PrintLPT()
{
}
/// <summary>
/// 打印命令,通过参数,可以打印小票打印机的一些命令,比如换行,行间距,打印位图等。
/// </summary>
/// <param name="byData">发送到端口的字节流</param>
/// <returns></returns>
public void Print(byte[] byData)
{
//如果端口为打开,则打印
if (iHandle.IsClosed == false)
{
fs.Write(byData, 0, byData.Length);
}
}
/// <summary>
/// 打印字符串
/// </summary>
/// <param name="strData">发送到端口的字符串</param>
public void Print(string strData)
{
//如果端口为打开,则打印
if (iHandle.IsClosed == false)
{
sw.Write(strData);
}
}
/// <summary>
/// 打印一行字符串
/// </summary>
/// <param name="strData">发送到端口的字符串</param>
public void PrintLine(string strData)
{
//如果端口为打开,则打印
if (iHandle.IsClosed == false)
{
sw.WriteLine(ESC_CMD.LineHeight + strData);
}
}
/// <summary>
/// 加粗打印一行
/// </summary>
/// <param name="strData">打印字符</param>
public void PrintWiderLine(string strData)
{
PrintLine(ESC_CMD.StrWiderStart + strData + ESC_CMD.StrWiderClear);
}
/// <summary>
/// 居中打印
/// </summary>
/// <param name="strData">打印字符</param>
/// <param name="withWider">是否带加粗效果</param>
/// <returns></returns>
public void PrintInMiddle(string strData, bool withWider)
{
if (withWider == true)
{
PrintWiderLine(ESC_CMD.StrMiddleStart + strData);
}
else
{
PrintLine(ESC_CMD.StrMiddleStart + strData);
}
Print(ESC_CMD.StrMiddelClear);//这里必须单独写,因为如果加粗字符未打印就设置加粗取消,则不会有加粗效果
}
/// <summary>
/// 打开端口
/// </summary>
/// <returns></returns>
public bool Open()
{
//以下两句是 IntPtr 转为 SafeFileHandle,否则不能使用 SafeFileHandle 定义 CreateFile
IntPtr handle = CreateFile(LPT_Name, 0x40000000, 0, 0, 3, 0, 0);
iHandle = new SafeFileHandle(handle, true); //进行指针到安全文件指针的转换,这是必须的。
if (iHandle.IsClosed == false)
{
fs = new FileStream(iHandle, FileAccess.ReadWrite);
sw = new StreamWriter(fs, Encoding.GetEncoding("GB2312"));
sw.Write(ESC_CMD.IniPrinter);//初始化打印机,使得打印设置恢复默认
return true;
}
else
{
return false;
}
}
/// <summary>
/// 关闭端口,走一段纸并立即提交缓存数据
/// </summary>
/// <returns></returns>
public void Close()
{
//if (paperFeed == true)
//{
// PaperFeed(200);//这里如果不走一段纸,那么最后打印的字符在撕纸后出不来
//}
sw.Close();
fs.Close();
}
/// <summary>
/// 关闭端口,走一段纸并立即提交缓存数据
/// </summary>
/// <param name="LastFeedLen">走纸长度</param>
public void Close(int LastFeedLen)
{
PaperFeed(LastFeedLen);
sw.Close();
fs.Close();
}
/// <summary>
/// 打开钱箱
/// </summary>
/// <returns></returns>
public void OpenMoneyBox()
{
try
{
if (iHandle.IsClosed == false)
{
sw.Write(((char)27).ToString() + "p" + ((char)0).ToString() + ((char)60).ToString() + ((char)255).ToString());
}
}
catch { }
}
/// <summary>
/// 走纸,通常2-5的数字,能刚好走出来
/// 其传入的数字表示要走几行
/// </summary>
/// <param name="n">走纸行数</param>
/// <returns></returns>
public void PaperFeed(int n)
{
Print(((char)27).ToString() + ((char)100).ToString() + ((char)n).ToString());
//(char)27 + (char)100 + (char)n n为走纸行数
}
/// <summary>
/// 打印票据,注意因POS机小票纸宽度问题只会打印前3列
/// 同时还要指定打印长度
/// </summary>
/// <param name="dtPrint">全部字段数据表</param>
/// <returns>true:打印成功 false:打印失败</returns>
public void PrintDataTable(System.Data.DataTable dtPrint)
{
try
{
if (iHandle.IsClosed == false)
{
string strForPrint;
for (int i = 0; i < dtPrint.Rows.Count; i++)
{
strForPrint = "";
strForPrint += PadRightEx(dtPrint.Rows[i][0].ToString(), 14, ' ');
strForPrint += PadRightEx(dtPrint.Rows[i][1].ToString(), 8, ' ');
strForPrint += PadLeftEx(dtPrint.Rows[i][2].ToString(), 8, ' ');
PrintLine(strForPrint);
}
}
}
catch
{
}
}
/// <summary>
/// 打印图片方法
/// </summary>
public void PrintImage(Image ig)
{
//获取图片
Bitmap bmp = new Bitmap(ig);
//设置字符行间距为n点行
//byte[] data = new byte[] { 0x1B, 0x33, 0x00 };
string send = "" + (char)(27) + (char)(51) + (char)(0);
byte[] data = new byte[send.Length];
for (int i = 0; i < send.Length; i++)
{
data[i] = (byte)send[i];
}
this.Print(data);
data[0] = (byte)'\x00';
data[1] = (byte)'\x00';
data[2] = (byte)'\x00'; // Clear to Zero.
Color pixelColor;
//ESC * m nL nH d1…dk 选择位图模式
// ESC * m nL nH
byte[] escBmp = new byte[] { 0x1B, 0x2A, 0x00, 0x00, 0x00 };
escBmp[2] = (byte)'\x21';
//nL, nH
escBmp[3] = (byte)(bmp.Width % 256);
escBmp[4] = (byte)(bmp.Width / 256);
//循环图片像素打印图片
//循环高
for (int i = 0; i < (bmp.Height / 24 + 1); i++)
{
//设置模式为位图模式
this.Print(escBmp);
//循环宽
for (int j = 0; j < bmp.Width; j++)
{
for (int k = 0; k < 24; k++)
{
if (((i * 24) + k) < bmp.Height) // if within the BMP size
{
pixelColor = bmp.GetPixel(j, (i * 24) + k);
if (pixelColor.R == 0)
{
data[k / 8] += (byte)(128 >> (k % 8));
}
}
}
//一次写入一个data,24个像素
this.Print(data);
data[0] = (byte)'\x00';
data[1] = (byte)'\x00';
data[2] = (byte)'\x00'; // Clear to Zero.
}
//换行,打印第二行
byte[] data2 = { 0xA };
this.Print(data2);
} // data
this.Print("\n\n");
}
/// <summary>
/// 左对齐中英文混排字符串,右边不够的用 char c补齐
/// </summary>
/// <param name="str">指定处理字符串</param>
/// <param name="totalByteCount">按半角计算的字符总长度</param>
/// <param name="c">补齐字符</param>
/// <returns></returns>
public string PadRightEx(string str, int totalByteCount, char c)
{
Encoding coding = Encoding.GetEncoding("gb2312");
int wordLen = 0;//已有的含中英文字符的长度
int byteLen = 0;//只计算半角字符的长度
int padLen = 0; //实际应计算减去Pad的长度
foreach (char ch in str.ToCharArray())
{
wordLen++;
if (coding.GetByteCount(ch.ToString()) == 2)
{//该字符是中文
byteLen += 2;
padLen += 1;
}
else
{
byteLen += 1;
//padLen += 1;
}
if (totalByteCount <= wordLen || totalByteCount <= byteLen)
{
str = str.Substring(0, wordLen);
break;
}
}
string w = str.PadRight(totalByteCount-padLen, c);
return w;
}
/// <summary>
/// 右对齐中英文混排字符串,左边不够的用 char c补齐
/// </summary>
/// <param name="str">指定处理字符串</param>
/// <param name="totalByteCount">字符总长度</param>
/// <param name="c">补齐字符</param>
/// <returns></returns>
public string PadLeftEx(string str, int totalByteCount, char c)
{
Encoding coding = Encoding.GetEncoding("gb2312");
int wordLen = 0;//已有的含中英文字符的长度
int byteLen = 0;//只计算半角字符的长度
int padLen = 0; //实际应计算减去Pad的长度
foreach (char ch in str.ToCharArray())
{
wordLen++;
if (coding.GetByteCount(ch.ToString()) == 2)
{//该字符是中文
byteLen += 2;
padLen += 1;
}
else
{
byteLen += 1;
//padLen += 1;
}
if (totalByteCount <= wordLen || totalByteCount <= byteLen)
{
str = str.Substring(0, wordLen);
break;
}
}
string w = str.PadLeft(totalByteCount-padLen, c);
return w;
}
}
}