Excel VBA与C#打印模板自动化对话记录
目录
Excel 2013宏编程语言说明
问题: Microsoft Office 2013只支持VBS来写宏程序吗?
回答: 不是。Microsoft Office 2013的主要宏编程语言是VBA(Visual Basic for Applications),而不是VBS(VBScript)。
VBA与VBS的区别
| 特性 | VBA | VBS |
|---|---|---|
| 运行环境 | 内嵌于Microsoft Office应用程序中 | 独立于Office,由Windows脚本宿主执行 |
| 文件格式 | 代码存储在Office文档内部(如.xlsm) | 独立的文本文件(.vbs) |
| 开发环境 | 强大的集成开发环境 | 任何文本编辑器(如记事本) |
| 主要用途 | 自动化、扩展Office功能 | 系统管理、自动化、网页脚本(已淘汰) |
| 对象模型 | 直接、无缝地访问Office对象模型 | 需要通过COM接口创建并操作Office对象 |
VBA开发工具与环境
问题: VBA的模块可以用Visual Studio Code来编写吗?
回答: 可以,但有局限性。VS Code可以作为VBA代码编辑器,但不能直接代替VBA编辑器运行和调试宏。
VS Code编写VBA的工作流程
- 安装VBA语言扩展:在VS Code扩展商店中搜索"VBA"安装相关插件
- 混合开发流程:
- 在VS Code中编写和编辑代码
- 在Office VBA编辑器(Alt+F11)中调试和执行
优点与局限性
优点:
- 更好的编辑体验
- 强大的搜索和导航
- 版本控制集成(Git)
- 多文件管理
局限性:
- 无法直接运行和调试
- 无法访问VBA对象模型
- IntelliSense功能有限
WPS表格求和函数
常用求和函数
-
SUM函数 - 基础求和
excel =SUM(A1:A10) =SUM(A1, A3, A5) =SUM(A1:A5, 100) -
SUMIF函数 - 单条件求和
excel =SUMIF(条件区域, 条件, [求和区域]) 示例:=SUMIF(A:A, "苹果", B:B) -
SUMIFS函数 - 多条件求和
excel =SUMIFS(求和区域, 条件区域1, 条件1, [条件区域2, 条件2], ...) 示例:=SUMIFS(C:C, A:A, "销售部", B:B, "张三")
快捷操作
- 使用「自动求和」按钮(∑)
- 快捷键:
Alt + =
Excel循环打印模板实现
业务场景
在Excel 2013中,有一个打印模板(Sheet2),数据源在Sheet1中(除表头外的具体行),需要通过点击按钮循环打印所有数据。
初始实现方案
Private Sub CommandButton1_Click()
Dim dataSheet As Worksheet
Dim printSheet As Worksheet
Dim lastRow As Long
Dim i As Long
Set dataSheet = ThisWorkbook.Worksheets("Sheet1")
Set printSheet = ThisWorkbook.Worksheets("PrintTemplate")
lastRow = dataSheet.Cells(dataSheet.Rows.Count, "A").End(xlUp).Row
Application.ScreenUpdating = False
For i = 2 To lastRow
' 将数据填充到模板
printSheet.Range("B4").Value = dataSheet.Cells(i, 1).Value
printSheet.Range("B5").Value = dataSheet.Cells(i, 2).Value
printSheet.Range("B6").Value = dataSheet.Cells(i, 3).Value
printSheet.Range("B7").Value = dataSheet.Cells(i, 4).Value
' 执行打印
printSheet.PrintOut Copies:=1
Next i
Application.ScreenUpdating = True
MsgBox "所有员工信息卡已发送到打印机!", vbInformation
End Sub
优化版本(从模板单元格读取参数)
Private Sub CommandButton1_Click()
Dim dataSheet As Worksheet
Dim printSheet As Worksheet
Dim startRow As Long, endRow As Long
Dim i As Long
Set dataSheet = ThisWorkbook.Worksheets("Sheet1")
Set printSheet = ThisWorkbook.Worksheets("PrintTemplate")
' 从模板单元格读取起始和结束行
startRow = printSheet.Range("B10").Value
endRow = printSheet.Range("B11").Value
' 如果未取得合法值,则使用默认值
If Not IsNumeric(startRow) Or startRow < 2 Then
startRow = 2
End If
If Not IsNumeric(endRow) Or endRow < startRow Then
endRow = dataSheet.Cells(dataSheet.Rows.Count, "A").End(xlUp).Row
End If
Application.ScreenUpdating = False
For i = startRow To endRow
' 填充数据到模板
printSheet.Range("B4").Value = dataSheet.Cells(i, 1).Value
' ... 其他字段填充
' 执行打印
printSheet.PrintOut Copies:=1
Next i
Application.ScreenUpdating = True
MsgBox "打印完成!", vbInformation
End Sub
C#调用Excel模板打印
方案一:使用Microsoft Office Interop
using Excel = Microsoft.Office.Interop.Excel;
public class ExcelTemplatePrinter
{
public void PrintUsingTemplate()
{
Excel.Application excelApp = null;
Excel.Workbook workbook = null;
try
{
excelApp = new Excel.Application();
excelApp.Visible = false;
string templatePath = @"C:\Templates\EmployeeCard.xlsx";
workbook = excelApp.Workbooks.Open(templatePath);
var worksheet = workbook.Worksheets[1];
// 填充数据
worksheet.Cells[4, 2].Value = "张三";
worksheet.Cells[5, 2].Value = "技术部";
// ... 其他字段
// 打印
worksheet.PrintOut(Copies: 1, Preview: false);
}
finally
{
// 清理资源
if (workbook != null)
{
workbook.Close(false);
System.Runtime.InteropServices.Marshal.ReleaseComObject(workbook);
}
if (excelApp != null)
{
excelApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp);
}
}
}
}
方案二:使用EPPlus
using OfficeOpenXml;
public class EPPlusTemplatePrinter
{
public void PrintUsingTemplate()
{
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var package = new ExcelPackage(new FileInfo(templatePath)))
{
var worksheet = package.Workbook.Worksheets[0];
// 填充数据
worksheet.Cells["B4"].Value = "张三";
worksheet.Cells["B5"].Value = "技术部";
// ... 其他字段
// 保存并打印
string tempFilePath = Path.GetTempFileName() + ".xlsx";
package.SaveAs(new FileInfo(tempFilePath));
PrintExcelFile(tempFilePath);
File.Delete(tempFilePath);
}
}
}
EPPlus库详细介绍
核心定义
EPPlus是一个用于操作Excel文件的.NET开源库,全称为Excel Programming Plus。
特点与优势
- 无需安装Microsoft Excel
- 高性能 - 直接处理XML结构,比Interop快
- 功能强大的API - 直观易用的编程模型
- 支持丰富的Excel功能:
- 单元格格式、公式、图表
- 数据透视表、数据验证
- 条件格式、保护、VBA支持
- 开源和免费(非商业用途)
与Interop对比
| 特性 | EPPlus | Microsoft.Office.Interop.Excel |
|---|---|---|
| 依赖环境 | 不需要安装Excel | 必须安装Microsoft Excel |
| 运行原理 | 直接操作Open XML文件 | 通过COM组件调用Excel应用程序 |
| 性能 | 高 | 低 |
| 部署场景 | 服务器、云端、桌面应用 | 主要限于桌面环境 |
| 资源管理 | 简单 | 复杂 |
VBA代码分析与优化
原始代码分析
Private Sub CommandButton2_Click() '''测试
Sheet2.Select
' ... 代码功能:从Sheet1读取数据,填充到Sheet2模板,按奇偶行分别填充到上下部分
End Sub
代码功能
该代码用于生成外箱标签,从Sheet1中读取数据,每张标签放置两条数据(上下排列),并统计生成的数量。
优化后的完整代码
Private Sub CommandButton3_Click() '''正式打印 - 优化版(修复合并单元格问题)
' 定义变量
Dim dataSheet As Worksheet
Dim templateSheet As Worksheet
Dim arr_s1 As Variant
Dim i As Long, lastRow As Long, arrLastRow As Long
Dim printCount As Long
Dim hasOddData As Boolean, hasEvenData As Boolean
Dim oddIndex As Long, evenIndex As Long
Dim msg As VbMsgBoxResult
' 设置工作表对象
Set dataSheet = Sheet1
Set templateSheet = Sheet2
' 用户确认对话框
msg = MsgBox("亲,是否要正式打印标签?", vbOKCancel + vbExclamation, "警告")
If msg = vbCancel Then
Exit Sub
End If
' 获取数据源
With dataSheet
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
If lastRow < 13 Then
MsgBox "数据源中没有有效数据!", vbExclamation, "提示"
Exit Sub
End If
arr_s1 = .UsedRange.Value
' 获取数组的实际行数
arrLastRow = UBound(arr_s1, 1)
End With
' 初始化模板
templateSheet.Select
' 安全地清空模板内容
Call ClearTemplateSafely(templateSheet)
templateSheet.Range("c12") = "备注:"
templateSheet.Range("c27") = "备注:"
' 关闭屏幕更新,提高性能
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
' 初始化计数器
printCount = 0
' 主循环:每次处理两条数据(一奇一偶)
For i = 13 To arrLastRow Step 2
DoEvents ' 保持响应
' === 安全的数据检查 - 修复边界问题 ===
' 检查当前奇数行是否有数据
hasOddData = (arr_s1(i, 2) <> "")
' 检查下一条偶数行是否有数据 - 安全处理边界情况
If i + 1 <= arrLastRow Then
hasEvenData = (arr_s1(i + 1, 2) <> "")
Else
hasEvenData = False ' 如果i+1超出数组边界,设为False
End If
' 如果两条数据都没有,跳过
If Not hasOddData And Not hasEvenData Then
GoTo ContinueLoop
End If
' --- 填充上半部分(奇数行数据) ---
If hasOddData Then
oddIndex = i
' 填充奇数行数据到模板上半部分
templateSheet.Range("b1") = "東莞塑合" '供应商
templateSheet.Range("b2") = "寧平中傑" '厂区
templateSheet.Range("b3") = arr_s1(oddIndex, 1) '序号
' 如果序号为空,使用前一行序号
If templateSheet.Range("b3") = "" And oddIndex > 13 Then
templateSheet.Range("b3") = arr_s1(oddIndex - 1, 1)
End If
templateSheet.Range("b4") = arr_s1(oddIndex, 23) 'PO
templateSheet.Range("b5") = arr_s1(oddIndex, 14) '企画单号
templateSheet.Range("b6") = arr_s1(oddIndex, 21) '材料名称
templateSheet.Range("b7") = arr_s1(oddIndex, 24) '颜色
templateSheet.Range("b8") = arr_s1(oddIndex, 22) '规格/数量
templateSheet.Range("b9") = Format(templateSheet.Range("i8"), "yyyy/mm/dd") '出货日期
templateSheet.Range("b10") = "MADE IN CHINA" '产地
templateSheet.Range("b11") = "" '原料批次
templateSheet.Range("b12") = Format(templateSheet.Range("i9"), "yyyy/mm/dd") '包装日期
templateSheet.Range("b13") = arr_s1(oddIndex, 7) & " KG / " & arr_s1(oddIndex, 8) & " KG" '净重/毛重
Else
' 如果没有奇数行数据,安全清空上半部分
Call ClearUpperSectionSafely(templateSheet)
End If
' --- 填充下半部分(偶数行数据) ---
If hasEvenData Then
evenIndex = i + 1
' 填充偶数行数据到模板下半部分
templateSheet.Range("b16") = "東莞塑合" '供应商
templateSheet.Range("b17") = "寧平中傑" '厂区
templateSheet.Range("b18") = arr_s1(evenIndex, 1) '序号
' 如果序号为空,使用前一行序号
If templateSheet.Range("b18") = "" And evenIndex > 13 Then
templateSheet.Range("b18") = arr_s1(evenIndex - 1, 1)
End If
templateSheet.Range("b19") = arr_s1(evenIndex, 23) 'PO
templateSheet.Range("b20") = arr_s1(evenIndex, 14) '企画单号
templateSheet.Range("b21") = arr_s1(evenIndex, 21) '材料名称
templateSheet.Range("b22") = arr_s1(evenIndex, 24) '颜色
templateSheet.Range("b23") = arr_s1(evenIndex, 22) '规格/数量
templateSheet.Range("b24") = Format(templateSheet.Range("i8"), "yyyy/mm/dd") '出货日期
templateSheet.Range("b25") = "MADE IN CHINA" '产地
templateSheet.Range("b26") = "" '原料批次
templateSheet.Range("b27") = Format(templateSheet.Range("i9"), "yyyy/mm/dd") '包装日期
templateSheet.Range("b28") = arr_s1(evenIndex, 7) & " KG / " & arr_s1(evenIndex, 8) & " KG" '净重/毛重
Else
' 如果没有偶数行数据,安全清空下半部分
Call ClearLowerSectionSafely(templateSheet)
End If
' --- 设置打印区域并执行打印 ---
templateSheet.PageSetup.PrintArea = "$A1:$D28"
templateSheet.PrintOut Copies:=1, Preview:=False
templateSheet.PageSetup.PrintArea = ""
' 计数
printCount = printCount + 1
ContinueLoop:
Next i
' 恢复应用程序设置
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
' 显示结果
MsgBox "打印完成!" & vbNewLine & _
"共计打印 " & printCount & " 张外箱标签。", vbInformation, "提示"
' 清理对象
Set dataSheet = Nothing
Set templateSheet = Nothing
End Sub
' ===== 安全清空模板的辅助函数 =====
' 安全清空整个模板
Private Sub ClearTemplateSafely(ws As Worksheet)
On Error Resume Next
ws.Range("b1").ClearContents
ws.Range("b2").ClearContents
' ... 逐单元格清空
ws.Range("b28").ClearContents
On Error GoTo 0
End Sub
' 安全清空上半部分
Private Sub ClearUpperSectionSafely(ws As Worksheet)
On Error Resume Next
ws.Range("b1").ClearContents
ws.Range("b2").ClearContents
' ... 逐单元格清空b1-b13
ws.Range("b13").ClearContents
On Error GoTo 0
End Sub
' 安全清空下半部分
Private Sub ClearLowerSectionSafely(ws As Worksheet)
On Error Resume Next
ws.Range("b16").ClearContents
ws.Range("b17").ClearContents
' ... 逐单元格清空b16-b28
ws.Range("b28").ClearContents
On Error GoTo 0
End Sub
优化要点总结
- 处理逻辑优化:成对处理数据(一奇一偶),立即打印
- 边界问题修复:安全处理数组边界,避免"下标越界"错误
- 合并单元格处理:使用辅助函数逐单元格清空,避免合并单元格错误
- 性能优化:关闭屏幕更新,使用数组处理数据
- 代码结构优化:规范的变量声明,完整的错误处理
- 资源管理:正确释放对象,恢复应用程序设置
关键修复点
-
边界检查:
vba If i + 1 <= arrLastRow Then hasEvenData = (arr_s1(i + 1, 2) <> "") Else hasEvenData = False End If -
合并单元格处理:
vba On Error Resume Next ' 逐单元格清空操作 On Error GoTo 0 -
循环范围:基于数组实际大小循环,避免不一致问题
总结
本次对话涵盖了从Excel VBA基础到高级优化的完整流程,包括:
- 基础知识:明确了VBA与VBS的区别,VBA开发工具选择
- 函数应用:介绍了WPS/Excel常用求和函数
- 业务实现:实现了Excel循环打印模板的完整方案
- 跨平台扩展:提供了C#调用Excel打印的多种方案
- 库介绍:详细介绍了EPPlus库的特点和优势
- 代码优化:对现有VBA代码进行了深度优化,解决了边界问题和合并单元格问题
最终得到的优化代码具有以下特点: - 健壮性:完善的错误处理和边界检查 - 高效性:性能优化措施确保快速执行 - 可维护性:清晰的结构和注释 - 用户体验:友好的提示和交互