WizardWu 编程网

一位台湾的程序员,研究 OOD、.NET 平台在企业信息化之应用、WCF & 工作流、性能调优、数据库。

博客园 首页 新随笔 联系 订阅 管理
  87 Posts :: 0 Stories :: 999 Comments :: 35 Trackbacks

教导如何用 C# 创建 Code 39 编码的「条码 (barcode)」图片,以供 ASP.NET + Crystal Reports 水晶报表呈现和打印此条码。本帖提供 ASP.NET 3.5 示例下载。

-------------------------------------------------
本帖的示例下载点:
http://files.cnblogs.com/WizardWu/100914.zip

执行本示例,需要 SQL Server 的 Northwind 数据库,以及 VS 2008 或 IIS,另还需要 Crystal Reports 2008 标准版 (SAP 公司的网站可下载完整的安装程序,无使用限制,但安装前需要输入安装序列号)。
若是 VS 2005/2008 内置的免费简易版 Crystal Reports,由于不具备「动态截取网络图片」的功能、无法抓取既有的条码图片,因此不适用本帖的教学。
---------------------------------------------------

日前做 ASP.NET 的项目用到 Crystal Reports 水晶报表,必须要能在浏览器中的报表显示和打印条码。原本我采用「字体 (font)」的方式产生条码 (水晶报表内置将某个数据库字段,直接转成条形码的功能),但后来发现这种做法,布署时必须在每一台客户端的 Windows 上安装特定的条码字体,如:free3of9 (可免费下载),才能在客户端浏览器正确显示和打印条码。因此后来弃用这种做法,改用「图片」的方式产生条码。

做法是先用 C# 和 .NET 的绘图 API,搭配一维条码最普遍的 Code 39 编码其规则,写一个可创建条码图片的 .ashx (HttpHandler) 或 .aspx,(这个文件放在报表的同一个 ASP.NET 项目里即可,不必发布成 service)。接着在 Crystal Reports 文件里,随便插入一张图片,透过水晶报表标准版才有的「动态截取网络图片」功能 (Visual Studio 内置的免费版水晶报表无此功能),去抓取这张已创建的条码图片,并要能动态传入参数,以让报表在换页时,条码可跟着变动内容。


首先用 C# 和 .NET 的绘图 API,搭配 Code 39 条码的编码规则,写一个可创建条码图片的组件。请参考本帖的下载示例,直接用浏览器开启 Code39Handler.ashx,并透过浏览器的 URL 地址栏,手动输入条码的参数作测试。执行结果和源代码 (这种组件通常是要钱的) 如下:



图 1 用 C# 和 .NET 的绘图 API,搭配 Code 39 编码规则产生的条码图片


以下代码,是用 C# 和 .NET 的绘图 API,搭配 Code 39 编码规则产生条码图片 (原版 VB 版作者为台湾的 阿達猴)。

Code39Handler
<%@ WebHandler Language="C#" Class="Code39Handler" %>

using System;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Text;

/// <summary>
/// 用 .NET 繪圖 API,搭配條碼最普遍的 Code 39 編碼規則 (一般超商的讀條碼機都可讀),產生條碼圖檔
/// </summary>
public class Code39Handler : IHttpHandler {
    
    
public void ProcessRequest (HttpContext context) {
        
//context.Response.ContentType = "text/plain";
        
//context.Response.Write("Hello World");

        
//Logic to retrieve the image file
        
//context.Response.ContentType = "image/jpeg";
        
//context.Response.WriteFile("MyImage01.jpg");

        
string mycode = context.Request["code"];

        
string 字串;
        
string 字元;
        
//字串 = "*-%$*"
        字串 = "*" + mycode + "*";  //Code 39 的特性是前、後置碼會標識「星號(*)」,表示開始和結束

        
int 畫布高 = 35;
        
int 畫布寬 = 0;
        
int 筆x = 0;
        
int 筆y = 20;
        
//int 筆寬 = 0;
        
        
if (!string.IsNullOrEmpty(mycode))
        {
            畫布寬 
= 字串.Length * 13;

            Bitmap BMP 
= new Bitmap(畫布寬, 畫布高, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
            Graphics G 
= Graphics.FromImage(BMP);
            G.TextRenderingHint 
= TextRenderingHint.AntiAlias;
            G.Clear(Color.White);

            Brush 筆刷1 
= new SolidBrush(Color.White);
            G.SmoothingMode 
= System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            G.FillRectangle(筆刷1, 
00, 畫布寬, 畫布高);

            
for (int i = 0; i < 字串.Length; i++)
            {
                
//取得 Code 39 碼的規則
                字元 = this.genBarcode(字串.Substring(i, 1).ToUpper());

                
for (int j = 0; j < 4; j++)
                {
                    
if (字元.Substring(j, 1).Equals("0"))
                    {
                        G.DrawLine(Pens.Black, 筆x, 
0, 筆x, 筆y);
                    }
                    
else
                    {
                        G.DrawLine(Pens.Black, 筆x, 
0, 筆x, 筆y);
                        G.DrawLine(Pens.Black, 筆x 
+ 10, 筆x + 1, 筆y);
                        筆x 
+= 1;
                    }

                    筆x 
+= 1;

                    
if (字元.Substring(j + 51).Equals("0"))
                    {
                        G.DrawLine(Pens.White, 筆x, 
0, 筆x, 筆y);
                    }
                    
else
                    {
                        G.DrawLine(Pens.White, 筆x, 
0, 筆x, 筆y);
                        G.DrawLine(Pens.White, 筆x 
+ 10, 筆x + 1, 筆y);
                        筆x 
+= 1;
                    }

                    筆x 
+= 1;
                } 
//end of loop

                
if (字元.Substring(41).Equals("0"))
                {
                    G.DrawLine(Pens.Black, 筆x, 
0, 筆x, 筆y);
                }
                
else
                {
                    G.DrawLine(Pens.Black, 筆x, 
0, 筆x, 筆y);
                    G.DrawLine(Pens.Black, 筆x 
+ 10, 筆x + 1, 筆y);
                    筆x 
+= 1;
                }

                筆x 
+= 2;
            } 
//end of loop

            
int x = 0;
            
int addx = 13;

            G.DrawString(
"-"new Font("Arial"10, FontStyle.Italic), SystemBrushes.WindowText, new PointF(x, 20));
            x 
+= addx;

            
for (int k = 0; k < mycode.Length; k++)
            {
                G.DrawString(mycode.Substring(k, 
1), new Font("Arial"10, FontStyle.Italic), SystemBrushes.WindowText, new PointF(x, 20));
                x 
= x + addx;
            }

            G.DrawString(
"-"new Font("Arial"10, FontStyle.Italic), SystemBrushes.WindowText, new PointF(x, 20));
            BMP.Save(context.Response.OutputStream, ImageFormat.Jpeg);
            G.Dispose();
            BMP.Dispose();

        }
        
else
        {
            畫布寬 
= 100;

            Bitmap BMP 
= new Bitmap(畫布寬, 畫布高, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
            Graphics G 
= Graphics.FromImage(BMP);
            G.TextRenderingHint 
= TextRenderingHint.AntiAlias;
            G.Clear(Color.White);
            
            
//未給參數時顯示的提示內容
            G.DrawString("無條碼產生"new Font("宋体"12, FontStyle.Regular), SystemBrushes.WindowText, new PointF(020));

            BMP.Save(context.Response.OutputStream, ImageFormat.Jpeg);
            G.Dispose();
            BMP.Dispose();
        }
    }


    
// 規則可參考網址 1:http://blog.csdn.net/xuzhongxuan/archive/2008/05/28/2489358.aspx
    
// 規則可參考網址 2:http://blog.163.com/zryou/blog/static/6903184200971704226450/
    /// <summary>
    
/// Code 39 碼的規則。
    
/// Code 39 碼可使用的字元如下:0~9、A~Z、+、-、*、/、%、$、. 及空白字元。    
    
/// </summary>
    
/// <param name="code"></param>
    
/// <returns></returns>
    public string genBarcode(string code)
    {
        
switch (code)
        {
            
case "0":
                code 
= "001100100";
                
break;
            
case "1":
                code 
= "100010100";
                
break;
            
case "2":
                code 
= "010010100";
                
break;
            
case "3":
                code 
= "110000100";
                
break;
            
case "4":
                code 
= "001010100";
                
break;
            
case "5":
                code 
= "101000100";
                
break;
            
case "6":
                code 
= "011000100";
                
break;
            
case "7":
                code 
= "000110100";
                
break;
            
case "8":
                code 
= "100100100";
                
break;
            
case "9":
                code 
= "010100100";
                
break;
            
case "A":
                code 
= "100010010";
                
break;
            
case "B":
                code 
= "010010010";
                
break;
            
case "C":
                code 
= "110000010";
                
break;
            
case "D":
                code 
= "001010010";
                
break;
            
case "E":
                code 
= "101000010";
                
break;
            
case "F":
                code 
= "011000010";
                
break;
            
case "G":
                code 
= "000110010";
                
break;
            
case "H":
                code 
= "100100010";
                
break;
            
case "I":
                code 
= "010100010";
                
break;
            
case "J":
                code 
= "001100010";
                
break;
            
case "K":
                code 
= "100010001";
                
break;
            
case "L":
                code 
= "010010001";
                
break;
            
case "M":
                code 
= "110000001";
                
break;
            
case "N":
                code 
= "001010001";
                
break;
            
case "O":
                code 
= "101000001";
                
break;
            
case "P":
                code 
= "011000001";
                
break;
            
case "Q":
                code 
= "000110001";
                
break;
            
case "R":
                code 
= "100100001";
                
break;
            
case "S":
                code 
= "010100001";
                
break;
            
case "T":
                code 
= "001100001";
                
break;
            
case "U":
                code 
= "100011000";
                
break;
            
case "V":
                code 
= "010011000";
                
break;
            
case "W":
                code 
= "110001000";
                
break;
            
case "X":
                code 
= "001011000";
                
break;
            
case "Y":
                code 
= "101001000";
                
break;
            
case "Z":
                code 
= "011001000";
                
break;
            
case "*":
                code 
= "001101000";
                
break;
            
case "-":
                code 
= "000111000"//好像辨識不出來
                break;
            
case "%":
                code 
= "100101000"//好像辨識不出來
                break;
            
case "$":
                code 
= "010101000"//好像辨識不出來
                break;
            
default:
                code 
= "010101000"//都不是就印 $
                break;
        }
        
        
return code;
    }
    
 
    
public bool IsReusable {
        
get {
            
return false;
        }
    }

}

 

执行 Default.aspx 里的水晶报表,结果如下图 2。条码是图片,不是字体,不必担心客户端的浏览器或打印机无法辨识某种条码字体。另下图 2 里,Employees 表的 EmployeeID 字段在数据库里是 int 类型,其传递至水晶报表默认会被当作 Number 类型,而自动显示小数点及后两位数字。本文后续会提到此问题的解法。



图 2 报表换页时,会传入不同的参数内容到我们写的条码组件里,因此条码内容也会跟着变动


若您想测试本帖示例,可去 SAP 公司的官方网站,下载标准版的 Crystal Reports 2008 软件 (下载页面标识的 Service Pack 版或 V1 版,若文件大小有 300 多至 500 多 MB,表示已内置安装主程序 + 修补程序,并非只有修补程序)。该软件和 Oracle 数据库的策略一样,提供网络下载完整的安装主程序、无使用时间限制或功能限制,但安装 Crystal Reports 前需要输入序列号 (怎么找序列号本文不赘述)。安装过程如下图 3,应加选「数据访问」的 ADO.NET 功能,以配合本帖示例的 ASP.NET 报表做法,透过网站 App_Code 文件夹里,事先定义好要访问的数据库内容的 .xsd (DataSet) 文件,作为设计 Crystal Reports 报表时的数据来源 (.xsd 为 XML 文件,这里面会依您写的 SQL 语句,了解报表要访问哪些表的哪些字段)。



图 3 Crystal Reports 2008 安装过程,加选「数据访问」的 ADO.NET 选项


如下图 4、图 5,在新建的水晶报表文件里,随便插入一个图片,在上面单击右键选择「设置图形格式」,再选择 Crystal Reports 标准版才有的「图形位置」功能。



图 4 在水晶报表里先随便插入一张图片



图 5 Crystal Reports 标准版才有的「图形位置」功能,VS 2005/2008 内置的版本无此功能


如下图 6,在水晶报表的「公式编辑器」里,输入以下内容和参数。此处动态传入的参数,会传入上图 1 里,我们事先用 C# 写好的条码生成组件。此例中,参数内容是 Employees 表的 EmployeeID 字段。若您报表里的条码,无法正确透过浏览器呈现,多半是这里的地址、端口号或内容打错,或此创建条码的服务未正确启用,此时浏览器只会显示原始插入报表的图片,而非条码。

下图 6、图 7 的公式编辑器里,及本帖提供的下载示例,在引用 Code39Handler.ashx 时站点的 URL 是硬编码的绝对路径,但我们可以改成动态赋值,如下:
"http://" & {?param1} & "/Code39Handler.ashx?code=" & {Employees.EmployeeID}

其中 ?param1 是我们从 ASP.NET Code-Behind,以 C# 传入的参数。我们可先用 C# 取得 Web server 机器的 IP 或 Domain Name,将其以字符串参数的方式,传入水晶报表的公式里,即可动态赋值,无须硬编码将 IP 写死在水晶报表的公式里。


图 6 公式编辑器里的语法,较类似 VB 或 VB.NET


另在上图 2 里,我们提到 EmployeeID 字段,在数据库里的类型是 int,在水晶报表里会被当作 Number 类型,而在条码里自动加上小数点及后两位数字。解决方式如下图 7,用 水晶报表自带的 CStr 函数,将该字段转型成字符串即可。



图 7 若报表里的条码,无法正确透过浏览器呈现,多半是这里的地址或内容打错,或这里未改成您 VS 2008 内置 Web server 或 IIS 执行时的正确地址、端口号


本文介绍的这种图片条码的做法,不只适用于 Crystal Reports 报表软件才能引用。理论上,其他厂牌的报表软件、网页程序,只要能链接至这个挂在 IIS 上提供服务的 Code39Handler.ashx 组件,就能在各自的报表或网页中,呈现此图片条码以供浏览和打印。


附带一提,在布署 ASP.NET 水晶报表至 IIS 时,必须将 IIS 默认目录:
C:\Inetpub\wwwroot\
底下的 aspnet_client 文件夹和里面的文件 (图 8),一并拷贝至我们的 ASP.NET 网站底下 (图 9),这样透过 IIS 执行的水晶报表,才能正确显示报表 Toolbar 里的 icon 按钮,并正确展示相关的功能。


图 8 此文件夹在安装完 Crystal Reports 主程序后,内容会自动增加



图 9 ASP.NET 网站布署至 IIS 时,必须一并将 aspnet_client 文件夹拷贝至网站的根目录

 

顺便提醒,水晶报表的打印方式分两种,一是透过报表 Toolbar 自带的打印按钮 (参考上图 2),二是自己撰码调用 ReportDocument 类的 PrintToPrinter 方法。

第一种打印方式,才能突破浏览器的安全限制,自动下载 ActiveX 程序以呈现打印预览的窗体,并能自动抓取到客户端的打印机名称。这种做法适合跨互联网打印,或需要在各自办公室里打印的用户。
第二种打印方式,虽然程序客制能力较强,但只能抓取服务器端的打印机名称,因此报表只能在服务器端打印或生成 PDF 文件。这种做法只适合同一个 LAN 或近距离 Intranet 共用打印机的用户。 
 

posted on 2010-09-14 03:19 WizardWu 阅读(3899) 评论(25) 编辑 收藏

Feedback

#1楼[楼主] 2010-09-14 03:26 WizardWu      
若有网友有其他 B/S 打印条码的做法,欢迎提供意见。

 回复 引用 查看   

#2楼 2010-09-14 08:50 szyicol      
有创意!



那标准版的下载地址和序列号是什么呢?收费的?

 回复 引用 查看   

#3楼 2010-09-14 08:52 Alex He      
最好用code39 extend因为code39有些字符不包括啊,网上有完整的demo
 回复 引用 查看   

#4楼 2010-09-14 08:53 szyicol      
下载是这个吗
http://www.businessobjects.com/campaigns/forms/downloads/crystal/2008/datasave.asp


http://downloads.businessobjects.com/akdlm/crystalreports/CrystalReports2008.zip

 回复 引用 查看   

#5楼[楼主] 2010-09-14 09:01 WizardWu      
这个是我在网络上找到的示例和 Code 39 规则,改写成 C# 版的。
事实上我也不确定打印后,条形码机是否能正确扫描和读取条码。
有兴趣的网友,欢迎帮忙测试一下。

 回复 引用 查看   

#6楼[楼主] 2010-09-14 09:10 WizardWu      
我是在一、两年前,在 CR 的原始公司 Business Object 还没被 SAP 收购前,所下载的 CR 2008 + SP1 (V1),文件大小共 393 MB。

去年 BO 公司被 SAP 收购后,原下载地址已不见。新版的下载地址,应是以下这个 (若无法连接,请重新整理浏览器看看) :

https://websmp130.sap-ag.de/sap%28bD1lbiZjPTAwMQ==%29/bc/bsp/spn/bobj_download/main.htm" target="_blank">https://websmp130.sap-ag.de/sap%28bD1lbiZjPTAwMQ==%29/bc/bsp/spn/bobj_download/main.htm

https://websmp130.sap-ag.de/sap(bD16aCZjPTAwMQ==)/bc/bsp/spn/bobj_download/main.htm

http://www.sdn.sap.com/irj/boc/downloads
http://service.sap.com/sap/bc/bsp/spn/bobj_download/main.htm

Crystal Repoerts 2008 已出到第 SP 3 版的。有兴趣的网友,可下载这个版本看看 :
Crystal Reports 2008 - Service Pack 3 Full Build
501.336 MB,或
Crystal Reports 2008 - Service Pack 3
333.656 MB

 回复 引用 查看   

#7楼 2010-09-14 09:14 海底咸鱼      
我前几天做了个ean13的条形码,插入到word的书签里,和商品上的条形码核对基本一致
http://www.cnblogs.com/chenry/archive/2010/09/11/1823720.html

 回复 引用 查看   

#8楼[楼主] 2010-09-14 09:26 WizardWu      
Thanks 楼上的网友们
 回复 引用 查看   

#9楼[楼主] 2010-09-14 09:49 WizardWu      
只要把这个创建条形码的 C# 组件 :
Code39Handler.ashx
透过 IIS 提供服务,别的程序透过地址 url 传参数给它,就能在网络上产生图片条形码,如 :
http://192.168.0.1:801/CRwithBarcode/Code39Handler.ashx?code=ABC123

理论上,其他厂牌的报表程序、网页程序,只要能找到这个地址,就能引用这个图片条形码,不一定非得要 Crystal Reports 才能引用此条码形

 回复 引用 查看   

#10楼 2010-09-14 11:13 Kevin Cheng      
另外,在服务器端用你的条码字体,将编码输出成图片也是一种解决方案。
 回复 引用 查看   

#11楼[楼主] 2010-09-14 11:19 WizardWu      
感谢 Kevin Cheng,您讲的也是另一种解法。
 回复 引用 查看   

#12楼 2010-09-14 14:08 ymmt      
好复杂啊
 回复 引用 查看   

#13楼[楼主] 2010-09-14 14:23 WizardWu      
之前在博问提问产生图形条码,
都没人理,自己研究好久。

 回复 引用 查看   

#14楼 2010-09-14 18:29 鹤冲天      
现在有好多开源的条码生成控件,
c#: http://barcoderender.codeplex.com/
jQuery:http://code.google.com/p/jquery-barcode/

有关条码、RFID等自动识别的问题,可以到

自动识别小组http://space.cnblogs.com/group/ai/讨论

 回复 引用 查看   

#15楼 2010-09-14 22:36 gulu      
LZ,我也有类似的问题,http://www.cnblogs.com/gulu/,TBarCode 9是否有接触过。我觉得使用TBarCode 9比用水晶报表方便多。不过就是还没有破解的,生成的图片有demo字样
 回复 引用 查看   

推荐+1,有水平
 回复 引用 查看   

#17楼[楼主] 2010-09-14 23:12 WizardWu      
感谢 鹤冲天 提供的资料。刚有下载源代码来看过。
并感谢楼上的网友们提供的资料。

 回复 引用 查看   

#18楼[楼主] 2010-09-15 20:17 WizardWu      
thanks all guys
 回复 引用 查看   

#19楼[楼主] 2010-09-17 13:00 WizardWu      
有用过本帖下载示例的网友,若有空的话,
麻烦请反馈,看条形码打印后,条形码机是否能正常读取?
谢谢各位。

 回复 引用 查看   

#20楼[楼主] 2010-09-22 13:44 WizardWu      
相关文章:
WPF 员工卡条形码
http://www.cnblogs.com/gnielee/archive/2010/07/26/wpf-employee-card-with-barcode.html

 回复 引用 查看   

#21楼 2010-09-28 15:42 小哈      
參考了樓主的寫法
用條碼閱讀器可以正常讀出
在此表示感謝!

 回复 引用 查看   

#22楼[楼主] 2010-09-28 21:29 WizardWu      
相当感谢网友「小哈」的反馈。
 回复 引用 查看   

#23楼[楼主] 2010-11-02 14:28 WizardWu      
报表已能成功捉到 Web server (IIS) 上的 IP。做法:
先用 C# 捉 Web server 上的 IP,再将其当作字符串参数 (如下的 ?param2),传入水晶报表里,产生条码的公式里:

产生条码的公式:
"http://" & {?param2} & "/vehicle/crystalreport/Code39Handler.ashx?code=" & CStr({@f_Barcode})

C# 捉 IP:
String strHostName = System.Net.Dns.GetHostName(); //取得本機名稱
System.Net.IPHostEntry iphostentry = System.Net.Dns.GetHostByName(strHostName); //取得本機的 IpHostEntry 類別實體
//Response.Write(iphostentry.AddressList[0].ToString());
rd1.SetParameterValue("param2", iphostentry.AddressList[0].ToString()); //10.2.1.46 丟入報表,當條碼圖片的捉資料來源

原始文章已作修改,加入了此一动态赋值的说明。

 回复 引用 查看   

#24楼 2011-01-13 15:34 空紫竹      
Study!!!
 回复 引用 查看   

#25楼[楼主] 2011-01-15 18:29 WizardWu      
thanks 空紫竹.
 回复 引用 查看