教导如何用 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 版作者为台湾的 阿達猴)。
using System;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Text;
///
/// 用 .NET 繪圖 API,搭配條碼最普遍的 Code 39 編碼規則 (一般超商的讀條碼機都可讀),產生條碼圖檔
///
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, 0 , 0 , 畫布寬, 畫布高);
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 + 1 , 0 , 筆x + 1 , 筆y);
筆x += 1 ;
}
筆x += 1 ;
if (字元.Substring(j + 5 , 1 ).Equals( " 0 " ))
{
G.DrawLine(Pens.White, 筆x, 0 , 筆x, 筆y);
}
else
{
G.DrawLine(Pens.White, 筆x, 0 , 筆x, 筆y);
G.DrawLine(Pens.White, 筆x + 1 , 0 , 筆x + 1 , 筆y);
筆x += 1 ;
}
筆x += 1 ;
} // end of loop
if (字元.Substring( 4 , 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 + 1 , 0 , 筆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( 0 , 20 ));
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/
///
/// Code 39 碼的規則。
/// Code 39 碼可使用的字元如下:0~9、A~Z、+、-、*、/、%、$、. 及空白字元。
///
///
///
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 标准版才有的「图形位置」功能。
图 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 写死在水晶报表的公式里。
另在上图 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 共用打印机的用户。