.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更稳定