Skip to content

.NET 操作 PowerPoint COM 组件:从零到精通的幻灯片制作实战

常见制作场景完整案例

问题

前面 10 篇文章拆解了 PPT 自动化中的每个技术点。这篇把知识点串起来,用三个真实业务场景演示"从数据到 PPT"的完整链路。

每个案例都遵守前一篇说到的两个原则:using/ComReleaser 管理 COM 生命周期(建议配合 ComReleaser 一起用),变量拆分避免链式调用。


案例 1:根据数据库数据批量生成产品介绍 PPT

场景:产品目录表中有 50 个产品,每个产品需要生成一页幻灯片,包含产品名称、图片、描述和价格。

数据模拟(实际工程中替换为数据库查询):

csharp
public record Product(
    string Name,
    string ImagePath,
    string Description,
    decimal Price
);

var products = new List<Product>
{
    new("智能手表 Pro", @"C:\Images\watch.jpg",
        "支持心率监测、GPS 定位、50 米防水", 2999),
    new("无线降噪耳机", @"C:\Images\earphone.jpg",
        "40dB 主动降噪、30 小时续航", 1299),
    new("便携式投影仪", @"C:\Images\projector.jpg",
        "1080P 分辨率、自动对焦、内置电池", 3999),
};

生成逻辑

csharp
using MudTools.OfficeInterop;
using MudTools.OfficeInterop.PowerPoint;
using MsCore = Microsoft.Office.Core;

public class ProductCatalogGenerator
{
    public void Generate(List<Product> products, string outputPath)
    {
        using var app = PowerPointFactory.BlankDocument();
        var pres = app.ActivePresentation;

        // 封面
        var cover = pres.AddSlide(PpSlideLayout.ppLayoutTitle, 1);
        cover.Shapes.Title.TextFrame.TextRange.Text = "产品目录";
        cover.Shapes[2].TextFrame.TextRange.Text = $"共 {products.Count} 款产品";
        cover.ApplyThemeColorScheme("Median");

        // 每产品一页
        for (int i = 0; i < products.Count; i++)
        {
            var product = products[i];
            var slide = pres.AddSlide(PpSlideLayout.ppLayoutBlank);

            // 产品名称
            var nameBox = slide.Shapes.AddTextbox(
                MsCore.MsoTextOrientation.msoTextOrientationHorizontal,
                30, 20, 500, 50);
            nameBox.TextFrame.TextRange.Text = product.Name;
            nameBox.TextFrame.TextRange.Font.Size = 28;
            nameBox.TextFrame.TextRange.Font.Bold = true;
            nameBox.TextFrame.TextRange.Font.NameFarEast = "微软雅黑";

            // 产品图片
            if (File.Exists(product.ImagePath))
            {
                slide.Shapes.AddPicture(
                    product.ImagePath, false, true,
                    30, 80, 300, 250);
            }

            // 描述
            var descBox = slide.Shapes.AddTextbox(
                MsCore.MsoTextOrientation.msoTextOrientationHorizontal,
                360, 80, 320, 150);
            descBox.TextFrame.TextRange.Text = product.Description;
            descBox.TextFrame.TextRange.Font.Size = 14;
            descBox.TextFrame.TextRange.Font.Color.RGB = 0x555555;

            // 价格
            var priceBox = slide.Shapes.AddTextbox(
                MsCore.MsoTextOrientation.msoTextOrientationHorizontal,
                360, 250, 200, 40);
            priceBox.TextFrame.TextRange.Text = $"¥{product.Price:N0}";
            priceBox.TextFrame.TextRange.Font.Size = 24;
            priceBox.TextFrame.TextRange.Font.Bold = true;
            priceBox.TextFrame.TextRange.Font.Color.RGB = 0xE74C3C;
        }

        pres.SaveAs(outputPath, PpSaveAsFileType.ppSaveAsOpenXMLPresentation);
    }
}

完成后调用 new ProductCatalogGenerator().Generate(products, @"C:\Reports\catalog.pptx")


案例 2:将 Markdown 转换为带样式的幻灯片

场景:技术文档用 Markdown 编写,需要一键转换成演示文稿。

Markdown 解析规则

# 标题 → 封面页
## 一级章节 → 章节分隔页
### 二级内容 → 内容页(标题 + 正文)
- 列表项 → 正文段落
csharp
using MudTools.OfficeInterop;
using MudTools.OfficeInterop.PowerPoint;
using MsCore = Microsoft.Office.Core;

public class MarkdownToPptConverter
{
    public void Convert(string markdownText, string outputPath)
    {
        using var app = PowerPointFactory.BlankDocument();
        var pres = app.ActivePresentation;

        var lines = markdownText.Split('\n');
        string? currentTitle = null;
        var currentBody = new List<string>();

        void FlushContent()
        {
            if (currentTitle == null) return;
            var slide = pres.AddSlide(PpSlideLayout.ppLayoutBlank);

            var titleBox = slide.Shapes.AddTextbox(
                MsCore.MsoTextOrientation.msoTextOrientationHorizontal,
                40, 30, 600, 50);
            titleBox.TextFrame.TextRange.Text = currentTitle;
            titleBox.TextFrame.TextRange.Font.Size = 30;
            titleBox.TextFrame.TextRange.Font.Bold = true;
            titleBox.TextFrame.TextRange.Font.Color.RGB = 0x1A5276;

            if (currentBody.Count > 0)
            {
                var bodyBox = slide.Shapes.AddTextbox(
                    MsCore.MsoTextOrientation.msoTextOrientationHorizontal,
                    40, 100, 600, 350);
                bodyBox.TextFrame.TextRange.Text = string.Join("\n", currentBody);
                bodyBox.TextFrame.TextRange.Font.Size = 16;
                bodyBox.TextFrame.TextRange.Font.Color.RGB = 0x333333;
                bodyBox.TextFrame.WordWrap = true;
            }
            currentBody.Clear();
        }

        foreach (var rawLine in lines)
        {
            var line = rawLine.Trim();
            if (string.IsNullOrEmpty(line)) continue;

            if (line.StartsWith("# "))
            {
                // 封面
                var cover = pres.AddSlide(PpSlideLayout.ppLayoutTitle);
                cover.Shapes.Title.TextFrame.TextRange.Text = line[2..];
                cover.Shapes[2].TextFrame.TextRange.Text = "自动生成文档";
                currentTitle = null;
            }
            else if (line.StartsWith("## "))
            {
                FlushContent();
                currentTitle = line[3..];
            }
            else if (line.StartsWith("- "))
            {
                currentBody.Add($"• {line[2..]}");
            }
            else
            {
                currentBody.Add(line);
            }
        }
        FlushContent(); // 最后一页

        pres.SaveAs(outputPath, PpSaveAsFileType.ppSaveAsOpenXMLPresentation);
    }
}

使用方式:

csharp
var markdown = """
# 2026 技术分享

## 架构演进

- 单体 → 微服务
- 容器化部署
- 可观测性体系

## 关键技术

- .NET 9 + Aspire
- Azure Kubernetes Service
- OpenTelemetry

## 成果

- 部署效率提升 80%
- 故障恢复时间缩短 90%
""";

new MarkdownToPptConverter().Convert(markdown, @"C:\Reports\tech-share.pptx");

案例 3:周报自动化生成(模板 + 数据 + 图表)

场景:每周一生成一份包含模板、数据表格和趋势图表的周报。

csharp
using MudTools.OfficeInterop;
using MudTools.OfficeInterop.PowerPoint;
using MsCore = Microsoft.Office.Core;
using MsPowerPoint = Microsoft.Office.Interop.PowerPoint;
using Excel = Microsoft.Office.Interop.Excel;

public class WeeklyReportGenerator
{
    public void Generate(string templatePath, string outputPath)
    {
        // 从模板加载
        using var app = PowerPointFactory.Open(templatePath);
        var pres = app.ActivePresentation;

        // 替换日期
        pres.ReplaceText("{{Week}}", $"第 {DateTime.Now.GetISOWeek()} 周");
        pres.ReplaceText("{{Date}}", DateTime.Now.ToString("yyyy-MM-dd"));

        // 第 2 页:关键指标表格
        var slide2 = pres.GetSlide(2);
        var tableShape = slide2.Shapes.AddTable(5, 4, 60, 120, 580, 200);
        var table = tableShape.Table;

        string[] headers = ["指标", "本周", "上周", "环比"];
        for (int c = 1; c <= 4; c++)
        {
            var cell = table.Cell(1, c);
            cell.Shape.TextFrame.TextRange.Text = headers[c - 1];
            cell.Shape.TextFrame.TextRange.Font.Bold = true;
            cell.Shape.TextFrame.TextRange.ParagraphFormat.Alignment =
                PpParagraphAlignment.ppAlignCenter;
            cell.Shape.TextFrame.VerticalAnchor = MsCore.MsoVerticalAnchor.msoAnchorMiddle;
        }

        var metrics = new (string Name, double Current, double Previous)[]
        {
            ("DAU", 45200, 42800),
            ("新增用户", 3800, 3500),
            ("转化率", 3.2, 2.9),
            ("营收(万)", 128, 115),
        };

        for (int r = 0; r < metrics.Length; r++)
        {
            int row = r + 2;
            var m = metrics[r];
            table.Cell(row, 1).Shape.TextFrame.TextRange.Text = m.Name;
            table.Cell(row, 2).Shape.TextFrame.TextRange.Text = m.Current.ToString(m.Name == "转化率" ? "F1" : "N0");
            table.Cell(row, 3).Shape.TextFrame.TextRange.Text = m.Previous.ToString(m.Name == "转化率" ? "F1" : "N0");

            var change = (m.Current - m.Previous) / m.Previous * 100;
            var changeCell = table.Cell(row, 4);
            changeCell.Shape.TextFrame.TextRange.Text = change >= 0
                ? $"↑ {change:F1}%"
                : $"↓ {Math.Abs(change):F1}%";
            changeCell.Shape.TextFrame.TextRange.Font.Color.RGB =
                change >= 0 ? 0x27AE60 : 0xE74C3C;

            foreach (int c in new[] { 1, 2, 3, 4 })
            {
                table.Cell(row, c).Shape.TextFrame.VerticalAnchor =
                    MsCore.MsoVerticalAnchor.msoAnchorMiddle;
            }
        }

        // 第 3 页:趋势图表
        var slide3 = pres.GetSlide(3);

        // 模拟最近 7 天数据
        var days = new[] { "周一", "周二", "周三", "周四", "周五", "周六", "周日" };
        var dauData = new[] { 41200, 42800, 44500, 43800, 45200, 46800, 47200 };

        var chartShape = slide3.Shapes.AddChart(
            XlChartType.xlLine,
            60, 120, 580, 340
        );

        var nativeShape = (MsPowerPoint.Shape)chartShape;
        nativeShape.Chart.ChartData.Activate();
        var wb = nativeShape.Chart.ChartData.Workbook;
        var ws = (Excel.Worksheet)wb.Sheets[1];

        ws.Cells[1, 1] = "日期";
        ws.Cells[1, 2] = "DAU";

        for (int i = 0; i < days.Length; i++)
        {
            ws.Cells[i + 2, 1] = days[i];
            ws.Cells[i + 2, 2] = dauData[i];
        }

        wb.Save();
        nativeShape.Chart.HasLegend = true;
        nativeShape.Chart.ChartStyle = 10;

        Console.WriteLine("趋势图已更新。");

        // 第 4 页:本周重点工作
        var slide4 = pres.GetSlide(4);
        slide4.Shapes.Title.TextFrame.TextRange.Text = "本周重点工作";

        var taskBox = slide4.Shapes.AddTextbox(
            MsCore.MsoTextOrientation.msoTextOrientationHorizontal,
            60, 100, 580, 300);
        taskBox.TextFrame.TextRange.Text = string.Join("\n",
            "1. 完成支付模块压力测试",
            "2. 修复线上订单同步延迟问题",
            "3. 启动 v3.0 版本需求评审"
        );
        taskBox.TextFrame.TextRange.Font.Size = 18;

        // 保存
        pres.SaveAs(outputPath, PpSaveAsFileType.ppSaveAsOpenXMLPresentation);
        Console.WriteLine($"周报已生成: {outputPath}");
    }
}

配置一个定时任务(Windows Task Scheduler 或 Hangfire),每周一早上 9 点执行:

csharp
new WeeklyReportGenerator().Generate(
    @"C:\Templates\WeeklyReport.potx",
    @$"C:\Reports\Weekly-{DateTime.Now:yyyyMMdd}.pptx"
);

mermaid
flowchart TD
    subgraph 案例1: 产品目录
    A1["数据库查询 Product 列表"] --> A2["创建封面页"]
    A2 --> A3["循环: 每个产品一页"]
    A3 --> A4["图片 + 名称 + 描述 + 价格"]
    A4 --> A5["保存 .pptx"]
    end

    subgraph 案例2: Markdown 转 PPT
    B1["读取 .md 文件"] --> B2["按 #/##/- 解析段落"]
    B2 --> B3["## → 内容页(标题+正文)"]
    B2 --> B4["# → 封面页"]
    B3 --> B5["逐页写入文本框"]
    B4 --> B5
    B5 --> B6["保存 .pptx"]
    end

    subgraph 案例3: 周报自动化
    C1["加载模板 .potx"] --> C2["ReplaceText 填充占位符"]
    C2 --> C3["添加数据表格 + 样式"]
    C3 --> C4["添加折线图 + Excel 数据绑定"]
    C4 --> C5["保存为 .pptx"]
    end

总结

这三个案例覆盖了 PPT 自动化中三个典型的模式:

  • 批量生成(案例1):数据驱动,每页结构相同,改内容不改样式
  • 格式转换(案例2):从一种格式到另一种格式,需要做内容解析和布局映射
  • 模板填充(案例3):基于预设模板,做占位符替换 + 数据注入

所有代码都遵循 MudTools 的两个核心原则:显式 Dispose COM 资源变量拆分避免链式调用。把这三个案例组合改造,可以应对 90% 以上的 PPT 自动化需求。

扩展阅读

  • 案例3 中用到的 DateTime.GetISOWeek() 扩展方法可以自行实现,或使用 System.Globalization.ISOWeek(.NET Core 3.0+)
  • 大数据量的批量生成建议使用 ComReleaser(第10篇实现)管理资源
  • 复杂图表推荐在 Excel 中先做好图表,再用 OLE 方式嵌入 PPT,比直接操作 ChartData.Workbook 更稳定