在VBA编程中巧用EXCEL模板生成样式可变报表

本文测试文档下载地址:百度文库

随着义教均衡发展工作的稳步推进,要求学校功能室实行规范化管理。若有报废或新增等仪器设备变动,则必须重新制作柜签等报表。Excel是微软公司Microsoft office办公软件的组件之一,它具有强大的制表功能且界面友好,可方便灵活地手工制作各式各样的柜签报表。也可使用Excel内置的系统开发工具VBA(Visual Basic for Application)对Excel对象直接编程,高效快速地制作柜签报表。
面对Excel工作表内动辄上万的数据行,通过手工多次复制粘贴数据制作柜签报表显然太麻烦了;而使用VBA编程,若直接通过代码向指定单元格写入数据,并控制生成的柜签报表样式(如字体、边框、行高列宽等),生成的柜签报表样式固定单一,修改报表,就要修改程序代码。能否把手工制表的方便灵活与VBA编程的高效快速统一起来呢?答案显然是肯定的。
本文结合自己的工作经验,介绍一种基于EXCEL模板生成样式可变的柜签报表的VBA编程方法。先手工制作柜签报表模板,通过编程复制粘贴所需数量的模板,并把数据写入模板指定单元格中。修改报表的样式,只需手工修改或添加新模板,几乎不需要改动代码,使用方便,操作简单,容易实现。

一、设计模板

在这里插入图片描述
图 1 数据表
图1是从广西教育装备平台下载到的数据(以下称数据表),第1行为字段,第2行起的每一行为1种仪器设备的相关信息。
在这里插入图片描述
图 2 柜签效果图
图2是柜签效果图,划分为页眉、正文和页脚三个区域。页眉区位于柜签的顶部,作为柜签的描述性信息,概括性的说明柜签报表的名称、存放房间、存放柜子等;页脚区位于柜签的底部,用于说明柜签的其它信息,如学校主管领导、制表人、制表时间等信息;正文区是柜签的主体部分,用于填充仪器设备的相关信息,如编号、名称、规格等信息。
在这里插入图片描述
图 3 原始模板
如图3所示,原始模板设计区可包含页眉、正文和页脚三个区,页眉区包含在4个“@”号界定的区域(即多行多列)或2个“@”号界定的区域(即一行多列);正文区包含在4个“#”号界定的区域(即多行多列)或2个“#”号界定的区域(即一行多列);页脚区包含在4个“&”号界定的区域(即多行多列)或2个“&”号界定的区域(即一行多列)。
可以在@、#、&号界定的区域内写入文字和字段,并设置单元格格式(如数字、对齐、字体及边框等)、行高和列宽等。字段是数据表中的字段,字段单独放在一个单元格或合并的单元格中并用“[ ]”号括住。因为每种仪器设备的制表样式都一样,所以正文区仅制作了1种仪器设备的样式。

二、复制粘贴模板

1、清除原始模板里的字段及界定符

为了原始模板的完整性,可以把该模板复制到新的工作表。为了保证粘贴的数据保留原有的格式(包括行高列宽都不能变)。可以先整行使用Copy方法进行复制粘贴,这样就可以保证行高一同被复制;使用PasteSpecial方法选择性粘贴,参数Paste设置为xlPasteColumnWidths即可保证列宽一同被复制。
代码如下:

'粘贴全部(除列宽外):主要目的是为了粘贴行高
 Worksheets("模板").Rows("1:8").Copy Worksheets("临时").Rows(1)
 '复制模板设计区
 Worksheets("模板").Range("A1:I8").Copy
 '选择性粘贴--列宽
 Worksheets("临时").Range("A1:I8").PasteSpecial xlPasteColumnWidths

循环模板设计区所有单元格,对存放字段和界定符的单元格执行“清除内容”操作。

'循环模板设计区内的每一个单元格
  For Each Rng In Worksheets("模板").Range("A1:I8")
      '清除字段
      If Left(Rng.Value, 1) & Right(Rng.Value, 1) = "[]" Then
          Rng.Value = ""
      End If
      
      '清除界定符
      If Rng.Value = "@" Or Rng.Value = "#" Or Rng.Value = "&" Then
         Rng.Value = ""
     End If
 Next

在这里插入图片描述
图4清除字段及界定符后的原始模板效果图

2、重组原始模板生成完整的柜签模板

在这里插入图片描述
图 5 完整的柜签模板效果图
将清除了字段和界定符后的原始模板按页眉、正文和页脚的顺序依次复制粘贴组合成完整的柜签模板,可以按前述方法确保粘贴的数据保留原有格式。代码如下:

'复制粘贴页眉区******************
  '粘贴全部(除列宽外):主要目的是为了粘贴行高
  Worksheets("临时").Rows("1:4").Copy Worksheets("报表").Rows(1)
  '********************************
  
  '复制粘贴正文区******************
  '用变量intTextCount记录用户输入的数量
  intTextCount = InputBox(Prompt:="请输入需复制正文区的数量", Title:="操作提示", Default:="10")
  
 '粘贴全部(除列宽外),行数由用户输入的正文区数量决定
 Worksheets("临时").Rows(5).Copy Worksheets("报表").Rows("5:" & 4 + intTextCount)
 '复制模板正文区
 Worksheets("临时").Range("B5:I5").Copy
 '选择性粘贴--列宽,扩展的行列数由正文区数量及模板正文区的行列数决定
 Worksheets("报表").Cells(5, "B").Resize(intTextCount, 7).PasteSpecial xlPasteColumnWidths
 '********************************
 
 '复制粘贴页脚区******************
 '粘贴全部(除列宽外):主要目的是为了粘贴行高
 Worksheets("临时").Rows("7:8").Copy Worksheets("报表").Rows(5 + intTextCount)
 '********************************

代码巧用InputBox函数实现人机交互,根据用户的输入准确选取粘贴区域,实现一次性复制多份正文区:粘贴区域的行数=模板正文区行数×行向份数,粘贴区域的列数=模板正文区列数×列向份数。比如本例正文区为1行7列,按图2柜签需要行向10份(用变量intTextCount表示)/列向1份共10份粘贴正文区,则粘贴区域应为1行×10份=10行、7列×1份=7列,即10行7列的区域,所以粘贴区域为Cells(5, “B”).Resize(intTextCount, 7)。生成完整的柜签模板如图5所示。

3、批量生成柜签模板

在这里插入图片描述
图 6 批量生成柜签模板效果图
如图6所示,可以参考仿照前述代码一次性复制粘贴多个柜签,并保证粘贴的数据保留原有格式,代码如下:

'用变量intTemplateColumnCount记录用户输入的模板列向份数
  intTemplateColumnCount = InputBox(Prompt:="请输入需复制的模板列向份数", Title:="操作提示", Default:="2")
  
  '由程序自行统计(统计过程省略)并用变量intTemplateRowCount记录模板行向份数
  intTemplateRowCount = 5
  
  '粘贴行高,粘贴区域由变量intTemplateRowCount决定
  Worksheets("报表").Rows("1:16").Copy Worksheets("报表").Rows("1:" & 16 * intTemplateRowCount)
 
 '复制模板:单元格区域为A1:H16
 Worksheets("报表").Range("A1:H16").Copy
 '选择性粘贴--列宽,扩展的行列数由模板行、列向份数及模板行、列数决定
 Worksheets("报表").Cells(1, "A").Resize(16 * intTemplateRowCount, 8 * intTemplateColumnCount).PasteSpecial xlPasteColumnWidths

代码继续通过最简单的人机交互函数InputBox,在程序运行时弹窗提示输入“模板列向份数”,以适应不同的页面设置。同时把输入值赋值给变量intTemplateColumnCount,由程序自动统计的“模板行向份数”赋值给变量intTemplateRowCount,而本例柜签模板单元格区域(A1:H16)为16行8列(实际程序可用变量引用该行列数),所以粘贴区域为Cells(1, “A”).Resize(16 * intTemplateRowCount, 8 * intTemplateColumnCount)。

三、向报表里的柜签模板填充数据

使用AdvancedFilter方法筛选数据(比如以存放柜子为条件筛选),在报表中明确填充该数据的单元格区域,通过For循环,用筛选出的相应数据替换“[ ]”号括住的字段。以填充正文区数据为例,代码如下:

'记录字段:写入模板的单元格行列号及数据表中的列号
  '字段:物品编号
  Arr(1, 1) = 1 '写入模板正文区的行号
  Arr(2, 1) = 2 '写入模板正文区的列号
  Arr(3, 1) = 1 '数据表中的列号
  
  '字段:名称
  Arr(1, 2) = 1 '写入模板正文区的行号
  Arr(2, 2) = 3 '写入模板正文区的列号
 Arr(3, 2) = 2 '数据表中的列号
 ……
 
 '记录筛选条件
 Worksheets("临时").Range("M1").Value = "存放柜子"
 Worksheets("临时").Range("M2").Value = "柜子20"
 '高级筛选出所需要的数据
 Worksheets("数据表").UsedRange.AdvancedFilter Action:=xlFilterCopy, _
                                      CriteriaRange:=Worksheets("临时").Range("M1:M2"), _
                               CopyToRange:=Worksheets("临时").Range("A1"), Unique:=False
 
 '统计并得出要写入上述筛选数据的柜签模板数量
 intCount = 3
 
 '向数量为intCount的柜签模板所在的单元格区域写入数据
 R = ((intCount - 1) \ intTemplateColumnCount) * 16 + 1 '起始行号
 L = ((intCount - 1) Mod intTemplateColumnCount) * 8 + 1 '起始列号
 With Worksheets("报表").Cells(R, L).Resize(16, 8)
     For j = 1 To intTextCount
         For i = 1 To UBound(Arr, 2)
             .Cells(j + 3 + Arr(1, i), Arr(2, i)).Value = Worksheets("临时").Cells(j + 1, Arr(3, i)).Value
         Next i
     Next
 End With

代码用数组Arr记录字段在柜签模板中的行列号及数据表中的列号,巧借字段架起柜签模板与数据表之间交互数据的桥梁,向柜签模板里填充数据时,根据数组Arr记录的行列号,可以很方便的把“[ ]”号括住的字段用数据表中同名字段对应行的数据进行替换。
在这里插入图片描述
图 7 巧用数学方法确定单元格行列号
代码还巧用数学方法确定填充数据的单元格区域。为方便说明问题,我把报表中的柜签模板简化为只占用一个单元格并按图7所示顺序编号,则其行列号可用公式计算:行号=(单元格编号-1)\ 列向份数 + 1、列号=(单元格编号 - 1) Mod 列向份数 + 1。如编号为18的单元格,其行号=(18-1)\ 5 + 1 = 4,列号=(18-1) Mod 5 + 1 =3,与实际吻合。根据柜签模板区的行列数(16行8列,实际程序中用变量调用)相应扩展即可计算编号为intCount柜签模板起始行列号:行号R = ((intCount - 1) \ intTemplateColumnCount) * 16 + 1 、列号L = ((intCount - 1) Mod intTemplateColumnCount) * 8 + 1。

四、修改柜签报表样式

柜签报表的样式不可能永恒不变,随着时间的推移、不同人员审美的差异,我们总是希望柜签报表的样式也能随之而改变。可以随时手工修改或添加模板并保证模板里的字段与数据表字段相对应,同时配合修改“模板列向份数”和“正文区份数”即可修改柜签报表的样式,而无须修改程序代码。
打印纸已设置了表格边框及标题,只需把数据套打到打印纸上,可以按图8所示设计模板。把正文区份数设置为1,可利用二维码控件(如QRMaker)生成包含二维码的物签(定位签)报表,如图9所示,“{}”号括住的字段用数据表中同名字段对应行的数据生成二维码。
在这里插入图片描述
图 8 生成柜签套打报表效果图
在这里插入图片描述 图 9 物签(定位签)报表效果图 不管是柜签报表还是物签(定位签)报表,生成报表的编程代码是相同的,不同之处在于模板的设计。使程序员从繁琐的编程工作中解放出来,把柜签等报表的样式决定权交给使用者,即把复杂的编程工作变成简便灵活的模板制作。既满足了我们对柜签、物签等报表样式多样化的需求,又大大减少了工作量,非常实用。