C#开发 ActiveX 小票打印控件

C#开发 ActiveX 小票打印控件

背景

去年帮朋友搞一个IE打印控件,在网上找了很多都不满足,要么是不能直接打印,要么是不能指定打印机,还有的是不能自定义样式。在网上折腾了一个周,还没搞定,于是想还不如自己开发一个吧。结果从现学C#到完成差不多只花了两周。过程中也遇到了很多坑,现在分享给大家,并且将项目在GitHub上开源。如果您觉得喜欢,给个Star,深表感谢。

所需软件

  • 宇宙IDE Visual Studio 开发并打包控件
  • Sublime Text 3 编辑 HTML 和 JavaScript 代码
  • Postman 模拟推送 json 数据

总计架构

框架示意图

控件开发

  • 添加Guid,例如: 073A987E-2A7C-4874-8BEE-321E04F4E84E,作为控件的classid
  • 代码初始化打印控件:
public Uc()
        {
            InitializeComponent();
            printDocument = new PrintDocument();
            printPreview = new PrintPreviewDialog();
            Margins margin = new Margins(1, 1, 1, 1);
            printDocument.DefaultPageSettings.Margins = margin;
            printDocument.DefaultPageSettings.PaperSize = new PaperSize("Custum", getInch(100), getInch(110));
            printDocument.PrintPage += new PrintPageEventHandler(this.printDocument_PrintPage);
            foodLength = 10;
            countLength = 7;
            moneyLength = 8;
        }
  • 获取并解析json数据,这里要用到C#解析json的库Newtonsoft.Json,通过NuGet包管理器安装,

1、在工具菜单下:
NuGet位置

2、在浏览菜单中搜索安装,这里我已经安装好了:
安装Newtonsoft.Json库

3、引用库:using Newtonsoft.Json;

按照面向对象的思想,我们需要定义实体类Ticket,具体解析代码如下:

public void getData(String json)
        {
            deserializedTicket = JsonConvert.DeserializeObject<Ticket>(json);
            isKitchen = deserializedTicket.isKitchen;
            Boolean isPreview = deserializedTicket.isPreview;
            String printName = deserializedTicket.printName;
            printDocument.PrinterSettings.PrinterName = printName;
            printPreview.Document = printDocument;
            lineReader = new StringReader(isKitchen ? getKitchenPrintString(deserializedTicket) : getTicketPrintString(deserializedTicket));

            try
            {
                if (isPreview)
                {
                    if (printPreview.ShowDialog() == DialogResult.OK)
                    {
                        printDocument.Print();
                    }
                }
                else
                {
                    printDocument.Print();
                }
            }
            catch (Exception excep)
            {
                MessageBox.Show(excep.Message, "打印出错", MessageBoxButtons.OK, MessageBoxIcon.Error);
                printDocument.PrintController.OnEndPrint(printDocument, new PrintEventArgs());
            }
        }
  • 打印小票,通过字符串拼接实现,代码如下:
        private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
        {
            Graphics g = e.Graphics; //获得绘图对象
            float yPosition = 0;   //绘制字符串的纵向位置
            int count = 0; //行计数器
            float leftMargin = e.MarginBounds.Left; //左边距
            float topMargin = e.MarginBounds.Top; //上边距
            string line = null; //行字符串
            Font printFont = isKitchen ? new Font(new FontFamily("宋体"), 14) : new Font(new FontFamily("宋体"), 10);
            Font notesFont = new Font(new FontFamily("宋体"), 12);
            Font typeFont = new Font(new FontFamily("黑体"), 20);
            SolidBrush brush = new SolidBrush(Color.Black); //刷子
            //逐行的循环打印
            while ((line = lineReader.ReadLine()) != null)
            {
                yPosition = topMargin + (count * printFont.GetHeight(g));
                if (line.StartsWith("注意"))
                {
                    g.DrawString(line, notesFont, brush, leftMargin, yPosition, new StringFormat());
                }
                else if (line.StartsWith("点菜") || line.StartsWith("加菜") || line.StartsWith("退菜"))
                {
                    g.DrawString(line, typeFont, brush, leftMargin, yPosition, new StringFormat());
                }
                else
                {
                    g.DrawString(line, printFont, brush, leftMargin, yPosition, new StringFormat());
                }
                count++;
            }
            lineReader = new StringReader(isKitchen ? getKitchenPrintString(deserializedTicket) : getTicketPrintString(deserializedTicket));
        }

        public string getTicketPrintString(Ticket ticket)
        {
            StringBuilder sb = new StringBuilder();
            string restaurant = ticket.restaurant;
            string orderNo = ticket.orderNo;
            string address = ticket.address;
            string pay = ticket.pay;
            string telephone = ticket.telephone;
            string mobilephone = ticket.mobilephone;
            List<Ticket.Menu> menu = ticket.menu;
            int count = menu.Count;
            decimal cost = 0.00M;
            sb.Append("         " + restaurant + "\n");
            sb.Append("\n");
            sb.Append("---------------------------------------------------------------\n");
            sb.Append("日期:" + DateTime.Now.ToShortDateString() + "  " + "单号:" + orderNo + "\n");
            sb.Append("---------------------------------------------------------------\n");
            sb.Append(padRightTrueLen(" 菜名", foodLength, ' ') + padRightTrueLen("数量", countLength, ' ') +
                padRightTrueLen("单价", moneyLength, ' ') + "小计" + "\n");
            for (int i = 0; i < count; i++)
            {
                decimal sum = Decimal.Parse(menu[i].count) * Decimal.Parse(menu[i].price);
                sb.Append(padRightTrueLen((menu[i].name), foodLength, ' ') +
                    padRightTrueLen((" " + menu[i].count), countLength, ' ') +
                    padRightTrueLen((menu[i].price), moneyLength, ' ') + sum + "\n");
                cost += sum;
            }
            sb.Append("---------------------------------------------------------------\n");
            sb.Append("数量:" + count + "   合计:" + cost + "\n");
            sb.Append("付款: 现金" + " " + pay);
            sb.Append("   现金找零:" + " " + (Decimal.Parse(pay) - cost) + "\n");
            sb.Append("---------------------------------------------------------------\n");
            sb.Append("地址:" + address + "\n");
            sb.Append("电话:" + telephone + "   手机:" + mobilephone + "\n");
            sb.Append("\n");
            sb.Append("         谢谢惠顾,欢迎下次光临         ");
            sb.Append("\n");
            return sb.ToString();
        }

这里只贴出了部分代码,完整代码请移步到GitHub。

打包安装文件

这里需要安装InstallShield,到官网下载安装即可。这里注意要和Visual Studio是同一个版本,安装好后新建一个InstallShield Limited Edition Project项目:
新建InstallShield Limited Edition Project

选择项目作为主输出:
主输出

在InstallShield Limited Edition Project项目上右键,生成安装文件,并安装:
编译

我们也可以看到我们生成的可执行文件:
可执行文件位置

测试

好了,终于可以测试打印了。哦,别着急,还要写HTML,不废话了,直接上效果:
界面效果

我用的是云巴推送,具体实现就不细说了,key要用你自己的了,代码如下:

<!DOCTYPE HTML>
<html>
<head>
  <title>Yunba JavaScript Over Socket.IO Demo</title>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
  <object id="print" classid="clsid:073A987E-2A7C-4874-8BEE-321E04F4E84E" 
    width="0" height="0" ></object>
  <!--[if lte IE 7]>
  <script type="text/javascript" src="javascripts/json2.js"></script>
  <![endif]-->
  <script type="text/javascript" src="javascripts/socket.io-1.3.5.min.js"></script>
  <script type="text/javascript" src="javascripts/yunba-js-sdk.js"></script>
  <script type="text/javascript" src="javascripts/jquery-1.10.2.min.js"></script>
  <script>
    var yunba = new Yunba({appkey: '请用你的key'});
    var refresh_flag = getCookie('refresh_flag') == null ? 0 : getCookie('refresh_flag');
    yunba.init(function (success) {
      if (success) {
        $('#msg').html('<div style="color:green">已连接上 socket</div>');
        $('#msg').append('<div style="color:green">SocketId: ' + yunba.socket.id) + '</div>';

          mqtt_connect();
      }
    }, function () {
      //连接断开后,调用该回调重连。
      $('#msg').html('<div style="color:green">连接恢复 socket</div>');
      $('#msg').append('<div style="color:green">SocketId: ' + yunba.socket.id) + '</div>';

      mqtt_connect();
    });

    yunba.set_message_cb (function (data) {
        document.getElementById("print").getData(data.msg);
        $('#msg_end').before('来自频道:' + data.topic);
        $('#msg_end').before('&nbsp;&nbsp;&nbsp;消息内容:' + data.msg);
        console.log(data.msg);
        console.log(data);
        $('#msg_end').before("\<br\/\>");

        if (data.presence) {
            console.log(data.presence);
        }
        msg_end.scrollIntoView();  
    });

    function mqtt_connect() {
      yunba.connect(function (success, msg) {
        if (success) {
          $('#connect_status').html('Connected Success !');
          $('#connect_status').css('color', 'green');
          if (refresh_flag == 0) {
            addCookie('refresh_flag', 1, 0);
            window.location.reload();
          }
        } else {
          alert(msg);
        }
      });
    }

    function mqtt_disconnect() {
      yunba.disconnect(function (success, msg) {
        if (success) {
          $('#connect_status').html('Disconnected Success !');
          $('#connect_status').css('color', 'red');
        } else {
          alert(msg);
        }
      });
    }

    function set_alias() {
        if ($('#alias_set').val() == '') {
            alert('请输入别名');
            return false;
        }

        var alias = $('#alias_set').val();

        yunba.set_alias({'alias': alias}, function (data) {
            if (data.success) {
                alert('set alias success');
                //window.location.reload();
            } else {
                alert(data.msg);
            }
        })
    }

    function get_alias() {
        yunba.get_alias( function(data) {
            $('#msg_end').before('get alias:' + data.alias);
            $('#msg_end').before("\<br\/\>");
            msg_end.scrollIntoView();
        })
    }

    //监听关闭浏览器窗口时要断开mqtt连接
    //  window.onbeforeunload = function(){
    //          yunba.disconnect();
    //  }

    function addCookie(objName, objValue, objHours){//添加cookie 
        var str = objName + "=" + escape(objValue); 
        if (objHours > 0) {//为0时不设定过期时间,浏览器关闭时cookie自动消失 
            var date = new Date(); 
            var ms = objHours * 3600 * 1000; 
            date.setTime(date.getTime() + ms); 
            str += "; expires=" + date.toGMTString(); 
        } 
        document.cookie = str; 
        //alert("添加cookie成功"); 
    } 

    function getCookie(objName){ //获取指定名称的cookie的值 
        var arrStr = document.cookie.split("; "); 
        for (var i = 0; i < arrStr.length; i++) { 
            var temp = arrStr[i].split("="); 
            if (temp[0] == objName) 
                return unescape(temp[1]); 
        } 
    } 

    function delCookie(name){//为了删除指定名称的cookie,可以将其过期时间设定为一个过去的时间 
        var date = new Date(); 
        date.setTime(date.getTime() - 10000); 
        document.cookie = name + "=a; expires=" + date.toGMTString(); 
    } 

  </script>
  <style>
    input {
      height: 30px;
      font-weight: bold;
    }

    input[type="radio"] {
      height: 15px;
    }

    .green {
      color: green;
    }

    fieldset {
      margin-top: 20px;
    }

    b {
      margin-left: 10px;
    }

    .clear {
      clear: both;
    }
  </style>
</head>
<body>
  <div style="float:left;width:50%">
    <fieldset style="height:100px;">
      <legend>WebSocket Info</legend>
      <div id="msg"></div>
    </fieldset>
  </div>
  <div style="float:left;width:50%">
    <fieldset style="height:100px;">
      <legend>Connect & Disconnect</legend>
      <div id="connect_status"></div>
      <input type="button" value="Connect" onclick="mqtt_connect();"/>
      <input type="button" value="Disconnect" onclick="mqtt_disconnect();"/>
    </fieldset>
  </div>
  <div class="clear"></div>
  <fieldset>
    <legend>Subscribe & Unsubscribe</legend>
    <div style="float:left;width:50%;">
      <fieldset style="height:200px;">
        <legend>收到消息</legend>
        <div id="msg_list" style="Overflow-y:scroll;height:180px;">
          <div id="msg_end" style="height:0px; overflow:hidden"></div>
        </div>
      </fieldset>
    </div>
  </fieldset>
  <fieldset>
      <legend>别名</legend>
      别名(Alias): <input type="text" placeholder="输入alias..." id="alias_set"/>

      <input type="button" value="Set Alias" onclick="set_alias();"/>
      <input type="button" value="Get Alias" onclick="get_alias();"/>
  </fieldset>
</body>
<html>

开测,打开Postman,同样 appkey 和 seckey 要用你自己的了:
postman测试

奇迹发生了,打开浏览器,就能看的打印预览:
小票预览

终于结束了,最后我们来看一下,真是打印的效果:
实物图

最后的最后,完整项目GitHub地址:
https://github.com/hfrommane/ActiveX-Print

如果您喜欢,在GitHub上给个Star。如有转载,请注明出处,感谢您!

发布了34 篇原创文章 · 获赞 12 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/hfrommane/article/details/54868968