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代码进行了深度优化,解决了边界问题和合并单元格问题

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