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的工作流程

  1. 安装VBA语言扩展:在VS Code扩展商店中搜索"VBA"安装相关插件
  2. 混合开发流程
  3. 在VS Code中编写和编辑代码
  4. 在Office VBA编辑器(Alt+F11)中调试和执行

优点与局限性

优点:

  • 更好的编辑体验
  • 强大的搜索和导航
  • 版本控制集成(Git)
  • 多文件管理

局限性:

  • 无法直接运行和调试
  • 无法访问VBA对象模型
  • IntelliSense功能有限

WPS表格求和函数

常用求和函数

  1. SUM函数 - 基础求和 excel =SUM(A1:A10) =SUM(A1, A3, A5) =SUM(A1:A5, 100)

  2. SUMIF函数 - 单条件求和 excel =SUMIF(条件区域, 条件, [求和区域]) 示例:=SUMIF(A:A, "苹果", B:B)

  3. 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。

特点与优势

  1. 无需安装Microsoft Excel
  2. 高性能 - 直接处理XML结构,比Interop快
  3. 功能强大的API - 直观易用的编程模型
  4. 支持丰富的Excel功能
  5. 单元格格式、公式、图表
  6. 数据透视表、数据验证
  7. 条件格式、保护、VBA支持
  8. 开源和免费(非商业用途)

与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

优化要点总结

  1. 处理逻辑优化:成对处理数据(一奇一偶),立即打印
  2. 边界问题修复:安全处理数组边界,避免"下标越界"错误
  3. 合并单元格处理:使用辅助函数逐单元格清空,避免合并单元格错误
  4. 性能优化:关闭屏幕更新,使用数组处理数据
  5. 代码结构优化:规范的变量声明,完整的错误处理
  6. 资源管理:正确释放对象,恢复应用程序设置

关键修复点

  1. 边界检查vba If i + 1 <= arrLastRow Then hasEvenData = (arr_s1(i + 1, 2) <> "") Else hasEvenData = False End If

  2. 合并单元格处理vba On Error Resume Next ' 逐单元格清空操作 On Error GoTo 0

  3. 循环范围:基于数组实际大小循环,避免不一致问题

总结

本次对话涵盖了从Excel VBA基础到高级优化的完整流程,包括:

  1. 基础知识:明确了VBA与VBS的区别,VBA开发工具选择
  2. 函数应用:介绍了WPS/Excel常用求和函数
  3. 业务实现:实现了Excel循环打印模板的完整方案
  4. 跨平台扩展:提供了C#调用Excel打印的多种方案
  5. 库介绍:详细介绍了EPPlus库的特点和优势
  6. 代码优化:对现有VBA代码进行了深度优化,解决了边界问题和合并单元格问题

最终得到的优化代码具有以下特点: - 健壮性:完善的错误处理和边界检查 - 高效性:性能优化措施确保快速执行 - 可维护性:清晰的结构和注释 - 用户体验:友好的提示和交互