以 .NET 創建 Code 39 條碼圖片 供水晶報表打印
教導如何用 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 編碼規則產生條碼圖片。

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, 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/
/// <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 軟件 (下載頁面標識的 SP 版或 V1 版,表示已內置安裝主程序 + 修補程序,并非只有修補程序)。該軟件和 Oracle 數據庫的策略一樣,提供網絡下載完整的安裝主程序、無使用時間限制或功能限制,但安裝 Crystal Reports 前需要輸入安裝序號 (怎么找序號本文不再贅述)。安裝過程如下圖 3,加選「數據訪問」的 ADO.NET 功能,以配合本帖示例的 ASP.NET 報表做法,透過網站 App_Code 文件夾里,事先定義好要訪問的數據庫內容的 .xsd (DataSet) 文件,作為設計 Crystal Reports 報表時的數據來源。
圖 3 Crystal Reports 2008 安裝過程,加選「數據訪問」的 ADO.NET 選項
如下圖 4、圖 5,在新建的水晶報表文件里,隨便插入一個圖片,在上面單擊右鍵選擇「設置圖形格式」,再選擇 Crystal Reports 標準版才有的「圖形位置」功能。
圖 4 在水晶報表里先隨便插入一張圖片
圖 5 Crystal Reports 標準版才有的「圖形位置」功能,VS 2005/2008 內置的版本無此功能
如下圖 6,在水晶報表的「公式編輯器」里,輸入以下內容和參數。此處動態傳入的參數,會傳入上圖 1 里,我們事先用 C# 寫好的條碼生成組件。此例中,參數內容是 Employees 表的 EmployeeID 字段。若您報表里的條碼,無法正確透過瀏覽器呈現,多半是這里的地址、端口號或內容打錯,或此創建條碼的服務未正確啟用,此時瀏覽器只會顯示原始插入報表的圖片,而非條碼。
圖 6 公式編輯器里的語法,較類似 VB 或 VB.NET
另在上圖 2 里,我們提到數據庫里的 EmployeeID 字段,類型是 int,在水晶報表里會被當作 Number 類型,而在條碼里自動加上小數點及后兩位數字。解決方式如下圖 7,用 水晶報表自帶的 CStr 函數,將該字段轉型成字符串即可。
圖 7 若報表里的條碼,無法正確透過瀏覽器呈現,多半是這里的地址或內容打錯,或這里未改成您 VS 2008 內置 Web server 或 IIS 執行時的正確地址、端口號
附帶一提,在布署 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 程序以呈現打印預覽的窗體,并能自動抓取到客戶端的打印機名稱。這種做法適合跨互聯網打印的用戶。
第二種打印方式,雖然客制能力較強,但只能抓取服務器端的打印機名稱,因此報表只能在服務器端打印。這種做法只適合同一個 LAN 或 Intranet 共用打印機的用戶。