一、前言
记录一次word导出的实现。以前倒是做过几次Excel导出,但这次有一些特殊的格式要求。
这次打算用word模板,先简单建了一个word,往里塞DataTable测试功能实现
二、使用Microsoft.Office.Interop.Word
using static System.Net.Mime.MediaTypeNames;
using Microsoft.Office.Interop.Word;
using Application = Microsoft.Office.Interop.Word.Application;
public class Export
{
public static void Word()
{
// 创建Word应用程序实例
Application wordApp = new Application();
wordApp.Visible = false; // 如果需要看到Word应用程序则设置为true
// 打开Word模板文件
Document doc = wordApp.Documents.Open(@"E:\temple.docx");
// 找到文档中的表格
Table table = doc.Tables[1];
// 假设表格有足够的行和列来接受数据
for (int i = 1; i < 4; i++) // 假设有3行数据
{
Row row = table.Rows[i]; // 从第二行开始,第一行可能是表头
for (int j = 1; j < 2; j++) // 假设每行有3列数据
{
row.Cells[j].Range.Text = $"Cell {i},{j}"; // 填充数据
}
}
// 保存文档
object savePath = @"E:\temple1.docx";
doc.SaveAs2(savePath);
// 关闭文档和Word应用程序
doc.Close();
wordApp.Quit();
// 释放对象
System.Runtime.InteropServices.Marshal.ReleaseComObject(doc);
System.Runtime.InteropServices.Marshal.ReleaseComObject(wordApp);
Console.WriteLine("完成");
}
}
测试结果
能满足功能需求,但是对于Office.Interop的索引感觉有点怪,列表,不管table、row、cell,索引全是1开始,习惯性的tables [0]直接挂壁,
这时候想起了之前用过的NPOI,拿来试试
三、NPOI实现
前后端先分别nuget一下NPOI
using NPOI.XWPF.UserModel;
using System.IO;
public class Export
{
public static void Word()
{
using (FileStream stream = File.OpenRead(@"E:\temple.docx"))
{
XWPFDocument doc = new XWPFDocument(stream);
var table = doc.Tables[0];
for (int i = 0; i < 3; i++) // 假设有3行数据
{
for (int j = 0; j < 1; j++)
{
table.GetRow(i).GetCell(j).SetText($"Cell {i},{j}");
}
}
//写文件
FileStream outFile = new FileStream(@"E:\temple1.docx", FileMode.Create);
doc.Write(outFile);
outFile.Close();
}
}
}
嗯,异曲同工,妙啊
四、NPOI实现插入行
现在的问题是,单元格只能用不能建,
使用表格没有的单元格,就报错。
如果我们数据10条、50条怎么弄,模板不能自定义,又挂壁
for (int i = 0; i < 3; i++) // 假设有3行数据
{
#region --测试区--这一次改动主要在这里
if (i == 2)
{
XWPFTableRow newRow = table.CreateRow();//测试1---新建
var oldrow = table.Rows[i - 1];
var newRow2 = oldrow.CloneRow();//测试2---拷贝
}
#endregion --测试区--end
for (int j = 0; j < 1; j++)
{
table.GetRow(i).GetCell(j).SetText($"Cell {i},{j}");
}
}
这新行写后面去了,插偏了,找找文档看看
CreateRow、AddRow、CloneRow都是到列表尾类。 AddNewRowBetween过时,连Office.Interop的InsertAfter都没。
正烦躁时,看到了InsertNewTableRow,就是要初始化单元格,试试
for (int i = 3; i < 10; i++) // 假设有3行数据
{
#region --测试区--这一次改动主要在这里
XWPFTableRow row = table.InsertNewTableRow(i);
//使用insertNewTableRow必须填充列,且列数必须统一
for (int j = 0; j < columns; j++)
{
row.CreateCell(); //创建列
}
#endregion --测试区--end
for (int j = 0; j < columns; j++)
{
table.GetRow(i).GetCell(j).SetText($"Cell {i},{j}");
}
}
完美
五、富文本不换行
加了一个占位符,想显示RichTextBox 录入的值。结果发现占位符替换后不换行,\n无效。
那就只有手动换行了。
//手动换行
string [] strs = str.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
strs.ToList().ForEach(p =>
{
run.AppendText(p);
run.AddCarriageReturn();//加回车
});
富文本按\n切段,循环加回车。
六、重构一下
using System;
using System.IO;
using System.Data;
using NPOI.XWPF.UserModel;
using System.Collections.Generic;
using Microsoft.Office.Interop.Word;
using DataTable = System.Data.DataTable;
public class Export
{
public static void Word(string templePath,string outPath,Dictionary<string,string> parameters,DataTable dt)
{
using (FileStream stream = File.OpenRead(templePath))
{
try
{
XWPFDocument doc = new XWPFDocument(stream);
var table = doc.Tables[0];
int columns = table.GetRow(0).GetTableCells().Count;//列数
//表格数据写入
for (int i = 3; i < dt.Rows.Count+3; i++) // 循环行写入
{
//原地建新行
XWPFTableRow row = table.InsertNewTableRow(i);
//--使用insertNewTableRow必须填充列,且列数必须统一
for (int j = 0; j < columns; j++)
{
row.CreateCell(); //创建列
}
//新行里写数据
for (int j = 0; j < columns; j++)
{
table.GetRow(i).GetCell(j).SetText(dt.Rows[i - 3][j].ToString());//单元格赋值
}
}
//模板占位符替换
TextReplace(parameters, doc);
//写文件
FileStream outFile = new FileStream(outPath, FileMode.Create);
doc.Write(outFile);
outFile.Close();
}
catch (Exception ex)
{
}
}
}
/// <summary>
/// 占位符 替换
/// </summary>
private static void TextReplace(Dictionary<string, string> parameters, XWPFDocument doc)
{
foreach (var parameter in parameters)
{
string str = parameter.Value;
//时间换格式
if (parameter.Key.Contains("time")) {
str = DateTime.Parse(str.ToString()).ToString("yyyy-MM-dd");
doc.FindAndReplaceText("[" + parameter.Key + "]", str);
}
//富文本 手动换行
else if (parameter.Key.Contains("notes"))
{
//找到段落
var search = doc.Paragraphs.Where(p => p.Text.Contains(parameter.Key)).First();
//创建文本对象
var run = search.CreateRun();
//移除占位
search.ReplaceText("[" + parameter.Key + "]", "");
//手动换行
string[] strs = str.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
strs.ToList().ForEach(p =>
{
run.AppendText(p);
run.AddCarriageReturn();//加回车
});
}
//字符串替换
else
doc.FindAndReplaceText("[" + parameter.Key + "]", str);
}
}
}
测试数据
private void test()
{
//测试数据
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add("totalprice", "1000.02");
dic.Add("comnotes", "第一行\n第二行\n第三行");
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("sortid", typeof(int)));
dt.Columns.Add(new DataColumn("name", typeof(string)));
dt.Columns.Add(new DataColumn("model", typeof(string)));
dt.Columns.Add(new DataColumn("unit", typeof(string)));
dt.Columns.Add(new DataColumn("counts", typeof(decimal)));
dt.Columns.Add(new DataColumn("price", typeof(decimal)));
dt.Columns.Add(new DataColumn("totalpc", typeof(decimal)));
dt.Columns.Add(new DataColumn("notes", typeof(string)));
for (int i = 0; i < 10; i++)
{
DataRow dr = dt.NewRow();
int sort = i + 1;
dr["sortid"]= sort.ToString();
dr["name"] = "name"+ sort.ToString();
dr["model"] = "model"+ sort.ToString();
dr["unit"] = "unit"+sort.ToString();
dr["counts"] = sort+1;
dr["price"] = sort+2;
dr["totalpc"] = sort+3;
dr["notes"] = sort+"测试";
dt.Rows.Add(dr);
}
//测试
Export.Word(@"E:\temple.docx", @"E:\temple1.docx", dic,dt);
}
效果
七、总结
本文记录了C#使用NPOI导出数据到Word模板过程中所遇到的三个问题:
1、datatable循环原地插入行
2、模板占位符批替换
3、富文本不换行解决办法
在此基础上,可以扩展复杂的word模板、树状多级DataTable数据导出。这次就记录到这里了。