65.9K
CodeProject 正在变化。 阅读更多。
Home

在 VB.NET 和 C# 中使用 Code39 条形码

starIconstarIconstarIconstarIconstarIcon

5.00/5 (16投票s)

2015 年 9 月 24 日

CPOL

10分钟阅读

viewsIcon

49133

downloadIcon

3441

本文将介绍如何在 VB.NET 和 C# 中创建 Code39 条形码

引言

本文的目的是创建一个简单的类,该类将根据输入的字符串生成 Code 39 条形码图像。

背景

我不会解释什么是 Code 39 条形码。如果您正在阅读此说明,很可能您已经知道它是什么。但为了唤起大家的记忆,根据维基百科,“Code 39(也称为 Alpha39、Code 3 of 9、Code 3/9、Type 39、USS Code 39 或 USD-3)是一种可变长度的离散条形码符号体系。” 仍根据维基百科上的信息,Code 39 规范定义了 43 个字符,包括:

  • 大写字母 (A 到 Z)
  • 数字 (0 到 9)
  • 以及一些特殊字符 (-, ., $, /, +, %, 和空格)。
  • 一个额外的字符(用“*”表示)用于开始和停止分隔符。

每个字符由九个元素组成:五个条和四个空。每个字符中的九个元素中有三个是宽的(二进制值 1),六个是窄的(二进制值 0)。例如,字母“D”编码为

D=bwbwBWbwB

其中 b 和 w 分别代表窄条和窄空;B 和 W 代表宽条和宽空。窄与宽的宽度比可在 1:2 到 1:3 之间选择。

全 ASCII Code 39

虽然“纯”Code 39 条形码仅限于上述 43 个字符,但 Code 39 有一个扩展,允许表示所有 ASCII 字符。此扩展称为全 ASCII Code 39。其中,符号 0-9、A-Z、“.”、“-”和空格与其在 Code 39 中的表示相同。小写字母、附加标点符号和控制字符通过 Code 39 的两个字符序列表示(参见下表)。

<!-- 行 # 2 --> <!-- 行 # 3 --> <!-- 行 # 4 --> <!-- 行 # 5 --> <!-- 行 # 6 --> <!-- 行 # 7 --> <!-- 行 # 8 --> <!-- 行 # 9 --> <!-- 行 # 10 --> <!-- 行 # 11 --> <!-- 行 # 12 --> <!-- 行 # 13 --> <!-- 行 # 14 --> <!-- 行 # 15 --> <!-- 行 # 16 --> <!-- 行 # 17 --> <!-- 行 # 18 --> <!-- 行 # 19 --> <!-- 行 # 20 --> <!-- 行 # 21 --> <!-- 行 # 22 --> <!-- 行 # 23 --> <!-- 行 # 24 --> <!-- 行 # 25 --> <!-- 行 # 26 --> <!-- 行 # 27 --> <!-- 行 # 28 --> <!-- 行 # 29 --> <!-- 行 # 30 --> <!-- 行 # 31 --> <!-- 行 # 32 --> <!-- 行 # 33 --> <!-- 行 # 34 --> <!-- 行 # 35 --> <!-- 行 # 36 --> <!-- 行 # 37 --> <!-- 行 # 38 --> <!-- 行 # 39 --> <!-- 行 # 40 --> <!-- 行 # 41 --> <!-- 行 # 42 --> <!-- 行 # 43 --> <!-- 行 # 44 --> <!-- 行 # 45 --> <!-- 行 # 46 --> <!-- 行 # 47 --> <!-- 行 # 48 --> <!-- 行 # 49 --> <!-- 行 # 50 --> <!-- 行 # 51 --> <!-- 行 # 52 --> <!-- 行 # 53 --> <!-- 行 # 54 --> <!-- 行 # 55 --> <!-- 行 # 56 --> <!-- 行 # 57 --> <!-- 行 # 58 --> <!-- 行 # 59 --> <!-- 行 # 60 --> <!-- 行 # 61 --> <!-- 行 # 62 --> <!-- 行 # 63 --> <!-- 行 # 64 --> <!-- 行 # 65 -->
编号 字符 编码   编号 字符 编码
0 NUL %U   64 @ %V
1 SOH $A   65 A A
2 STX $B   66 B B
3 ETX $C   67 C C
4 EOT $D   68 D D
5 ENQ $E   69 E E
6 ACK $F   70 F F
7 BEL $G   71 G G
8 BS $H   72 H H
9 HT $I   73
10 LF $J   74 J J
11 VT $K   75 K K
12 FF $L   76 L L
13 CR $M   77 M M
14 SO $N   78 N N
15 SI $O   79 O O
16 DLE $P   80 P P
17 DC1 $Q   81 Q Q
18 DC2 $R   82 R R
19 DC3 $S   83 S S
20 DC4 $T   84 T T
21 NAK $U   85 U U
22 SYN $V   86 V V
23 ETB $W   87 W W
24 CAN $X   88 X X
25 EM $Y   89 Y Y
26 SUB $Z   90 Z Z
27 ESC %A   91 [ %K
28 FS %B   92 \ %L
29 GS %C   93 ] %M
30 RS %D   94 ^ %N
31 US %E   95 _ %O
32 [空格] [空格]   96 ` %W
33 ! /A   97 a +A
34 " /B   98 b +B
35 # /C   99 c +C
36 $ /D   100 d +D
37 % /E   101 e +E
38 & /F   102 f +F
39   /G   103 g +G
40 ( /H   104 h +H
41 ) /I   105 i +I
42 * /J   106 j +J
43 + /K   107 k +K
44 , /L   108 l +L
45 - -   109 m +M
46 . .   110 n +N
47 / /O   111 o +O
48 0 0   112 p +P
49 1 1   113 q +Q
50 2 2   114 r +R
51 3 3   115 s +S
52 4 4   116 t +T
53 5 5   117 u +U
54 6 6   118 v +V
55 7 7   119 w +W
56 8 8   120 x +X
57 9 9   121 +Y
58 : /Z   122 z +Z
59 ; %F   123 { %P
60 < %G   124 | %Q
61 = %H   125 } %R
62 > %I   126 ~ %S
63 ? %J   127 DEL %T,%X,%Y,%Z

校验和

Code 39 可以有一个可选的模 43 校验位。要计算校验和位,每个字符都被分配一个值(参见下表)。

<!-- 行 # 2 --> <!-- 行 # 3 --> <!-- 行 # 4 --> <!-- 行 # 5 --> <!-- 行 # 6 --> <!-- 行 # 7 --> <!-- 行 # 8 --> <!-- 行 # 9 --> <!-- 行 # 10 --> <!-- 行 # 11 --> <!-- 行 # 12 --> <!-- 行 # 13 --> <!-- 行 # 14 --> <!-- 行 # 15 --> <!-- 行 # 16 --> <!-- 行 # 17 --> <!-- 行 # 18 --> <!-- 行 # 19 --> <!-- 行 # 20 --> <!-- 行 # 21 --> <!-- 行 # 22 --> <!-- 行 # 23 -->
字符   字符
0 0   M 22
1 1   N 23
2 2   O 24
3 3   P 25
4 4   Q 26
5 5   R 27
6 6   S 28
7 7   T 29
8 8   U 30
9 9   V 31
A 10   W 32
B 11   X 33
C 12   Y 34
D 13   Z 35
E 14   - 36
F 15   . 37
G 16   [空格] 38
H 17   $ 39
18   / 40
J 19   + 41
K 20   % 42
L 21      

校验和计算方法如下:

  • 获取条形码中每个字符的值(0 到 42),不包括开始和停止代码。
  • 将这些值相加。
  • 将结果除以 43。
  • 余数是需要附加的校验字符的值。

上述算法也取自维基百科。

既然我们对 Code 39 条形码中数据编码方式有了一定的了解,那么让我们看看如何构建我们的类。在本文中,我将展示 VB.NET 代码,但同样的类也将提供 C# 版本供下载。

让我们首先将名为 Barcode39 的类分配给命名空间 Barcodes。如果运气好的话,我很快会发布另一篇文章,展示如何创建 Code128 条形码,我也会将其放在同一个命名空间中。

在下面的代码中,窄条和宽条的宽度值由 `WIDEBAR_WIDTH` 和 `NARROWBAR_WIDTH` 常量持有。这些值将窄条和宽条的比例设置为 1:2。

每个字符的符号(bwbw 字符序列)存储在一个 Hashtable 对象(名为 `mEncoding`)中,其中键是字符本身,值是字符的符号。此 Hashtable 对象在类构造函数中加载。

我们还有一个包含条形码符号体系编码的 43 个字符的 Char 元素数组。此数组中的元素与上表镜像,并且该数组也在构造函数中加载。

Option Explicit On
Option Strict On


Namespace Barcodes
   Public Class Barcode39

      Private Const WIDEBAR_WIDTH As Short = 2
      Private Const NARROWBAR_WIDTH As Short = 1
      Private Const NUM_CHARACTERS As Integer = 43

      Private mEncoding As Hashtable = New Hashtable
      Dim mCodeValue(NUM_CHARACTERS - 1) As Char


      Public Sub New()
         '       Character, symbol
         mEncoding.Add("*", "bWbwBwBwb")
         mEncoding.Add("-", "bWbwbwBwB")
         mEncoding.Add("$", "bWbWbWbwb")
         mEncoding.Add("%", "bwbWbWbWb")
         [.]
         mEncoding.Add("X", "bWbwBwbwB")
         mEncoding.Add("Y", "BWbwBwbwb")
         mEncoding.Add("Z", "bWBwBwbwb")

         mCodeValue(0) = "0"c
         mCodeValue(1) = "1"c
         mCodeValue(2) = "2"c
         [.]
         mCodeValue(40) = "/"c
         mCodeValue(41) = "+"c
         mCodeValue(42) = "%"c
      End Sub
   End Class
End Namespace

形成扩展字符串

下一个任务是构建一个相当简单的函数,该函数将检查输入字符串并检查是否存在标准 43 个字符之外的字符。如果存在,它将在其前面添加所需的特殊字符。该函数将返回输入字符串的扩展版本。

      Private Function ExtendedString(ByVal s As String) As String
         Dim Ch As Char
         Dim KeyChar As Integer
         Dim retVal As String = ""

         For Each Ch In s
            KeyChar = Asc(Ch)
            Select Case KeyChar
               Case 0
                  retVal &= "%U"
               Case 1 To 26
                  retVal &= "$" & Chr(64 + KeyChar)
               Case 27 To 31
                  retVal &= "%" & Chr(65 - 27 + KeyChar)
               Case 33 To 44
                  retVal &= "/" & Chr(65 - 33 + KeyChar)
               Case 47
                  retVal &= "/O"
               Case 58
                  retVal &= "/Z"
               Case 59 To 63
                  retVal &= "%" & Chr(70 - 59 + KeyChar)
               Case 64
                  retVal &= "%V"
               Case 91 To 95
                  retVal &= "%" & Chr(75 - 91 + KeyChar)
               Case 96
                  retVal &= "%W"
               Case 97 To 122
                  retVal &= "+" & Chr(65 - 97 + KeyChar)
               Case 123 To 127
                  retVal &= "%" & Chr(80 - 123 + KeyChar)
               Case Else
                  retVal &= Ch
            End Select

         Next
         Return retVal

      End Function

计算校验和

`Checksum` 函数遍历扩展字符串,对于其中的每个字符,它获取其值(这基本上与字符在 `mCodeValue` 数组中的索引一致),并将其添加到整数变量中。然后,该函数返回总和与 43 相除的余数。

Private Function CheckSum(ByVal sCode As String) As Integer
 Dim CurrentSymbol As Char
 Dim Chk As Integer
 For j As Integer = 0 To sCode.Length - 1
	CurrentSymbol = sCode.Chars(j)
	Chk += GetSymbolValue(CurrentSymbol)
 Next
 Return Chk Mod (NUM_CHARACTERS)
End Function

Private Function GetSymbolValue(ByVal s As Char) As Integer
 Dim k As Integer

 For k = 0 To NUM_CHARACTERS - 1
	If mCodeValue(k) = s Then
	   Return k
	End If
 Next
 Return Nothing
End Function

附加属性

为了使该类更灵活,我为其添加了一些属性。以下是列表:

<!-- 行 # 2 --> <!-- 行 # 3 --> <!-- 行 # 4 --> <!-- 行 # 5 -->
名称 类型 描述
显示字符串 布尔值 如果设置为 True,将在条形码图片下方添加编码字符串。
包含校验和位 布尔值 如果为 true,将计算校验和。
TextFont 字体 当 ShowString = True 时打印的编码字符串的字体
TextColor Color 当 ShowString = True 时打印的编码字符串的颜色

上述属性将在构建条形码图片时使用。让我们将它们添加到类中。

Public ShowString As Boolean
Public IncludeCheckSumDigit As Boolean
Public TextFont As New Font("Courier New", 7)
Public TextColor As Color = Color.Black

核心部分

该类公开了一个公共函数 `GenerateBarcodeImage`,该函数将创建并准备一个 Graphics 对象,条形码图片将由另一个 Sub (DrawBarcode) 绘制到该对象上,并返回一个 Image 对象。该函数接受 3 个参数:

  • 图像的高度 – 请记住,这将包括打印字符串所需的空间
  • 图像的宽度 – 为其分配一个适当的值,否则条形码图片可能无法容纳在 Image 对象中
  • 原始字符串

`GenerateBarcodeImage` 创建一个大小为 `ImageWidth` x `ImageHeight` 的 `PictureBox` 对象,并使用其中的 Graphics 对象进行绘制。然后,它组成扩展字符串,并通过添加前导和尾随星号以及(如有必要)校验位来格式化它。如果 `ShowString` 属性为 True,它还会写入原始字符串,最后调用 `DrawBarcode` 过程来绘制条形码并返回包含条形码图片的 Image 对象。

     Public Function GenerateBarcodeImage(ByVal ImageWidth As Integer,
                                          ByVal ImageHeight As Integer,
                                          ByVal OriginalString As String) As Image

         '-- create a image where to paint the bars
         Dim pb As PictureBox
         pb = New PictureBox
         With pb
            .Width = ImageWidth
            .Height = ImageHeight
            pb.Image = New Bitmap(.Width, .Height)
         End With
         '---------------------

         'clear the image and set it to white background
         Dim g As Graphics = Graphics.FromImage(pb.Image)
         g.Clear(Color.White)


         'get the extended string
         Dim ExtString As String
         ExtString = ExtendedString(OriginalString)


         '-- This part format the sring that will be encoded
         '-- The string needs to be surrounded by asterisks 
         '-- to make it a valid Code39 barcode
         Dim EncodedString As String
         Dim ChkSum As Integer
         If IncludeCheckSumDigit = False Then
            EncodedString = String.Format("{0}{1}{0}", "*", ExtString)
         Else
            ChkSum = CheckSum(ExtString)

            EncodedString = String.Format("{0}{1}{2}{0}",
                                          "*", ExtString, mCodeValue(ChkSum))
         End If
         '----------------------

         '-- write the original string at the bottom if ShowString = True
         Dim textBrush As New SolidBrush(TextColor)
         If ShowString Then
            If Not IsNothing(TextFont) Then
               'calculates the height of the string
               Dim H As Single = g.MeasureString(OriginalString, TextFont).Height
               g.DrawString(OriginalString, TextFont, textBrush, 0, ImageHeight - H)
               ImageHeight = ImageHeight - CShort(H)
            End If
         End If
         '----------------------------------------

         'THIS IS WHERE THE BARCODE DRAWING HAPPENS
         DrawBarcode(g, EncodedString, ImageWidth, ImageHeight)

         'IMAGE OBJECT IS RETURNED
         Return pb.Image

      End Function

`DrawBarcode` 的工作原理相当简单。对于编码字符串中的每个字符,它从 `mEncoding` Hashtable 获取符号。正如我们所知,这个符号是 b、w、B 和 W 字符的序列,对于这些字符中的每一个,函数都使用 Graphics 对象的 `FillRectangle` 方法创建一个具有适当宽度和颜色的条。这个适当的宽度和颜色由 `getBCSymbolWidth` 和 `getBCSymbolColor` 函数确定。

    Private Sub DrawBarcode(ByVal g As Graphics,
                            ByVal EncodedString As String,
                            ByVal Width As Integer,
                            ByVal Height As Integer)

         'Start drawing at 1, 1
         Dim XPosition As Short = 0
         Dim YPosition As Short = 0

         Dim CurrentSymbol As String = String.Empty

         '-- draw the bars
         For j As Short = 0 To CShort(EncodedString.Length - 1)
            CurrentSymbol = EncodedString.Chars(j)
            
            Dim EncodedSymbol As String = mEncoding(CurrentSymbol).ToString

            For i As Short = 0 To CShort(EncodedSymbol.Length - 1)
               Dim CurrentCode As String = EncodedSymbol.Chars(i)

               g.FillRectangle(getBCSymbolColor(CurrentCode), 
				XPosition, YPosition, getBCSymbolWidth(CurrentCode), Height)

               XPosition = XPosition + getBCSymbolWidth(CurrentCode)
            Next

            'After each written full symbol we need a whitespace (narrow width)
            g.FillRectangle(getBCSymbolColor("w"), XPosition, Yposition,
                            getBCSymbolWidth("w"), Height)
            XPosition = XPosition + getBCSymbolWidth("w")
            
         Next
         '--------------------------

      End Sub

     Private Function getBCSymbolColor(ByVal symbol As String) As System.Drawing.Brush
         If symbol = "W" Or symbol = "w" Then
            getBCSymbolColor = Brushes.White
         Else
            getBCSymbolColor = Brushes.Black
         End If
      End Function

      Private Function getBCSymbolWidth(ByVal symbol As String) As Short
         If symbol = "B" Or symbol = "W" Then
            getBCSymbolWidth = WIDEBAR_WIDTH
         Else
            getBCSymbolWidth = NARROWBAR_WIDTH
         End If
      End Function

就是这样。仅为说明目的,我添加了一个非常简单的窗体,它使用该类并包含在可下载文件中。

祝您编码愉快,

Stefano Castelli

© . All rights reserved.