傳統用程式碼逐儲存格寫入 Excel (Cells[row, col] = value) 既慢又難維護:格式、合併儲存格、公式、圖表、分頁列印… 每一項都要寫代碼控制。本篇介紹 「模板 + 宏」 架構:先在 Excel 設計好完整樣板 (含格式、公式、條件格式、樞紐分析表…),程式只負責「填資料、觸發宏」、最後另存新檔。開發效率提升 10 倍,報表維護由業務/財務人員直接在 Excel 調整,無需改程式碼重新部署。
┌──────────────┐ 填入基礎參數 ┌──────────────┐
│ 程式端 │ ──────────────────→ │ Excel 模板 │
│ (C# / VB) │ (Sheet2: 日期/部門) │ (三個工作表) │
└──────────────┘ └──────┬───────┘
│
▼ 觸發巨集 (Macro)
┌────────────────────┐
│ Macro: 複製樣板列、│
│ 填入明細資料、 │
│ 自動擴展公式區域 │
└────────┬───────────┘
│
▼
┌────────────────────┐
│ 另存為 .xlsx → │
│ 交付使用者/列印 │
└────────────────────┘
=SUM(C8:C9)SUM(C8:C10))、格式自動繼承| A1 | B1 | C1 |
|---|---|---|
| sheetdate | department | oper |
程式寫入單列資料:`A2=2024/01/15`, `B2=資訊部`, `C2=王小明`
| 員工編號 | 姓名 | 出勤天數 | 加班時數 | ... |
|---|---|---|---|---|
| E001 | 張三 | 22 | 10 | ... |
程式一次性寫入所有明細資料 (DataTable → 陣列 → Range.Value2)
Option Explicit
Sub GenerateReport()
' 關閉螢幕更新、事件、自動計算 → 极速執行
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
On Error GoTo ErrHandler
Dim wsRpt As Worksheet: Set wsRpt = ThisWorkbook.Sheets("Sheet1")
Dim wsData As Worksheet: Set wsData = ThisWorkbook.Sheets("Sheet3")
' 1. 取得明細資料筆數 (假設 A 欄有資料即為有效列)
Dim lastRow As Long
lastRow = wsData.Cells(wsData.Rows.Count, "A").End(xlUp).Row
If lastRow < 2 Then GoTo ExitSub ' 無資料
Dim dataRows As Long: dataRows = lastRow - 1 ' 扣除標題列
' 2. 在樣板列 (第 8 列) 下方插入 (dataRows - 1) 列
' 因已有 2 列樣板 (8,9),需再插 dataRows-2 列
If dataRows > 2 Then
wsRpt.Rows("9:9").Resize(dataRows - 2).Insert Shift:=xlDown, _
CopyOrigin:=xlFormatFromLeftOrAbove
End If
' 3. 將明細資料一次性寫入報表資料區 (第 8 列起)
Dim srcRange As Range
Set srcRange = wsData.Range("A2").Resize(dataRows, 20) ' 假設 20 欄
Dim destRange As Range
Set destRange = wsRpt.Range("A8").Resize(dataRows, 20)
destRange.Value2 = srcRange.Value2 ' 陣列賦值,極快
' 4. 強制重新計算 (更新彙總公式、條件格式)
Application.Calculation = xlCalculationAutomatic
wsRpt.Calculate
' 5. 可選:列印預覽、另存 PDF、Email 發送等
' wsRpt.ExportAsFixedFormat xlTypePDF, "C:\Reports\Monthly.pdf"
ExitSub:
Application.ScreenUpdating = True
Application.EnableEvents = True
Exit Sub
ErrHandler:
MsgBox "巨集執行錯誤: " & Err.Description, vbCritical
Resume ExitSub
End Sub
using System;
using System.Data;
using System.Data.SqlClient;
using Excel = Microsoft.Office.Interop.Excel;
public class ExcelReportGenerator
{
private readonly string _templatePath = @"C:\Templates\AttendanceReport.xlsm"; // 巨集啟用活頁簿
private readonly string _outputDir = @"C:\Reports\";
public string Generate(DateTime reportDate, string deptId, string operatorName)
{
Excel.Application xlApp = null;
Excel.Workbook wb = null;
try
{
xlApp = new Excel.Application { Visible = false, DisplayAlerts = false };
wb = xlApp.Workbooks.Open(_templatePath, ReadOnly: false); // 必須 ReadOnly=false 才能寫入/儲存
// 1. 寫入參數到 Sheet2
var wsParam = (Excel.Worksheet)wb.Sheets["Sheet2"];
wsParam.Range["A2"].Value2 = reportDate.ToString("yyyy/MM/dd");
wsParam.Range["B2"].Value2 = deptId;
wsParam.Range["C2"].Value2 = operatorName;
// 2. 寫入明細資料到 Sheet3 (高效能陣列寫入)
var wsData = (Excel.Worksheet)wb.Sheets["Sheet3"];
DataTable dt = GetAttendanceDetail(reportDate, deptId); // 從 DB 取資料
if (dt.Rows.Count > 0)
{
object[,] arr = new object[dt.Rows.Count, dt.Columns.Count];
for (int r = 0; r < dt.Rows.Count; r++)
for (int c = 0; c < dt.Columns.Count; c++)
arr[r, c] = dt.Rows[r][c];
var dest = wsData.Range["A2"].Resize[dt.Rows.Count, dt.Columns.Count];
dest.Value2 = arr; // 一次性寫入
}
// 3. 執行巨集 (產生報表格式、擴展公式)
xlApp.Run("GenerateReport"); // 宏名稱
// 4. 另存為 .xlsx (移除巨集、壓縮體積)
string outputFile = $"{_outputDir}Attendance_{deptId}_{reportDate:yyyyMMdd}.xlsx";
wb.SaveAs(outputFile, Excel.XlFileFormat.xlOpenXMLWorkbook); // .xlsx 非 .xlsm
return outputFile;
}
finally
{
wb?.Close(false);
xlApp?.Quit();
// 釋放 COM 物件
if (wb != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(wb);
if (xlApp != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);
GC.Collect(); GC.WaitForPendingFinalizers();
}
}
private DataTable GetAttendanceDetail(DateTime date, string deptId)
{
// 實際從 SQL Server 取資料
var dt = new DataTable();
using (var conn = new SqlConnection("YourConnectionString"))
using (var cmd = new SqlCommand("sp_GetAttendance", conn) { CommandType = CommandType.StoredProcedure })
{
cmd.Parameters.AddWithValue("@Date", date);
cmd.Parameters.AddWithValue("@DeptId", deptId);
using (var da = new SqlDataAdapter(cmd)) da.Fill(dt);
}
return dt;
}
}
| 技巧 | 說明 | 效能提升 |
|---|---|---|
陣列賦值 Range.Value2 = arr | 取代迴圈逐 Cell 寫入 | 100-1000x |
| 關閉 ScreenUpdating/Events/Calculation | 巨集執行期間不重繪、不觸發事件、不即時計算 | 10-50x |
| 插入列而非逐列寫入格式 | Excel 自動複製格式、擴展公式 | 避免格式代碼 |
| ReadOnly=false 開啟模板 | 允許寫入並另存 | 必要條件 |
| 另存為 .xlsx 非 .xlsm | 移除巨集程式碼、檔案較小、安全性較高 | 體積 -50% |
C:\Windows\System32\config\systemprofile\Desktop 及 C:\Windows\SysWOW64\config\systemprofile\Desktop)。.xlsm,且信任中心 → 巨集設定 → 勾選「信任 VBA 專案物件模型存取」。