C# + OpenXML 生成 Word 文檔(VS2015)

本文是 Open XML SDK 的入門文章,將逐步講解如何生成一個下面這樣的 Word 文檔:
在這裏插入圖片描述有關 Open XML SDK 的詳細內容,請參閱這個鏈接:歡迎使用 Open XML SDK 2.5 for Office

創建示例程序

啓動 Visual Studio 2015,新建一個 Windows 窗體應用程序。在窗體上放一個按鈕,用於生成 Word 文檔。
在這裏插入圖片描述

添加 Open XML SDK 引用

解決方案資源管理器裏,在項目名稱下面的引用節點上單擊右鍵,在彈出菜單上選擇管理 NuGet 程序包,打開NuGet 包管理器
在這裏插入圖片描述
NuGet 包管理器瀏覽已安裝更新三個頁面,點擊瀏覽切換到瀏覽頁面。在瀏覽下面的搜索框裏輸入 openxml,搜索結果列表的第一項 DocumentFormat.OpenXml 就是我們要找的 Open XML SDK。
在這裏插入圖片描述
版本信息顯示當前版本是 2.9.1,點擊右側的安裝按鈕,安裝 Open XML SDK。
安裝完畢後,切換到已安裝頁面,會發現已安裝列表裏出現了 DocumentFormat.OpenXml

創建 Word 文檔

創建 Word 文檔,使用 WordprocessingDocument.Create(fileName, WordprocessingDocumentType.Document) 方法,該方法創建一個空白的文檔。

MainDocumentPart 與 Body

使用 Create() 方法創建一個新文檔,並得到 WordprocessingDocument 對象,該對象表示 Word 文檔包。我們要爲文檔添加文字和表格,但文字和表格並不能直接添加到 WordprocessingDocument 對象裏,還要做以下三步:

  1. WordprocessingDocument 對象增加主文檔部件
  2. 創建主文檔部件的 Document 對象
  3. Document 對象添加 Body 對象

然後,再把文字和表格等添加到 Body 對象裏。

using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(fileName, WordprocessingDocumentType.Document))
{
    MainDocumentPart mainPart = wordDocument.AddMainDocumentPart();
    mainPart.Document = new Document();
    Body docBody = mainPart.Document.AppendChild(new Body());
}

設置紙張大小和方向

新建的文檔,默認是縱向的,在我們這個示例中,需要設置爲橫向,並且設置紙張尺寸爲 A4 大小。
要設置整個文檔的打印方向,需要給 Body 添加一個 SectionProperties 對象,再給 SectionProperties 添加一個 PageSize 對象,並設置 PageSizeOrientPageOrientationValues.Landscape
僅設置 OrientLandscape,並不能使打印方向變爲橫向,還需要同時設置 PageSizeWidthHeight

設置寬度和高度

A4 紙的大小是 21cm x 29.7cm,是不是把 PageSizeWidth 設爲 29.7,Height 設爲 21 就可以了呢?仔細看一下,WidthHeight 的類型是 UInt32Value,也就是說必須是整數。實際上這還不是重點,真正的重點是,PageSizeWidthHeight 的單位並不是釐米,而是 二十分之一磅。我們來換算一下,1 釐米≈28.35磅,那麼 29.7×28.35×20≈16839,21×28.35×20≈11907。所以,要把紙張設爲 A4 橫向大小,需要設置 Width16839Height11907

docBody.AppendChild(
    new SectionProperties(
        new PageSize() { Width = 16839U, Height = 11907U, Orient = PageOrientationValues.Landscape }
    ));

輸出文字

這個文檔的內容,包括兩行文字、一個表格和一張圖片。下面先來輸出文字。
對於文字,Open XML 是以 段落(Paragraph)連續文本(Run)文本(Text) 三級結構來表示的。一個 Paragraph 包含一個或多個 Run,一個 Run 包含一個或多個 Text。

下面就來輸出這兩行文字:

Paragraph titlePara = docBody.AppendChild(new Paragraph());
Run titleRun = titlePara.AppendChild(new Run());
titleRun.AppendChild(new Text("陳桑桑的日常家務雜事"));

Paragraph datePara = docBody.AppendChild(new Paragraph());
Run dateRun = datePara.AppendChild(new Run());
dateRun.AppendChild(new Text("5 月 27 日至 6 月 1 日"));

輸出結果如下:
在這裏插入圖片描述

設置文字字體、大小、顏色

上面的代碼雖然輸出了文字,但並不美觀。我們還要爲文字設置字體、大小、顏色。
要設置文字的字體、大小、顏色,需要給 Run 添加 RunProperties

RunProperties titleRunProp = titleRun.AppendChild(new RunProperties());

RunFonts titleFonts = new RunFonts() { Ascii = "宋體", HighAnsi = "宋體", EastAsia = "宋體" };
FontSize titleSize = new FontSize() { Val = "36" }; //設置字體大小爲18磅,以半磅爲單位
DocumentFormat.OpenXml.Wordprocessing.Color titleColor = new DocumentFormat.OpenXml.Wordprocessing.Color() { Val = "365F91" };

titleRunProp.AppendChild(titleFonts);
titleRunProp.AppendChild(titleSize);
titleRunProp.AppendChild(titleColor);
RunProperties dateRunProp = dateRun.AppendChild(new RunProperties());

RunFonts dateFonts = new RunFonts() { Ascii = "宋體", HighAnsi = "宋體", EastAsia = "宋體" };
FontSize dateSize = new FontSize() { Val = "24" }; //設置字體大小爲12磅,以半磅爲單位
DocumentFormat.OpenXml.Wordprocessing.Color dateColor = new DocumentFormat.OpenXml.Wordprocessing.Color() { Val = "365F91" };

dateRunProp.AppendChild(dateFonts);
dateRunProp.AppendChild(dateSize);
dateRunProp.AppendChild(dateColor);

需要注意

  • RunProperties 必須是 Run 的第一個子元素,也就是說需要先添加 RunProperties,後添加 Text
  • FontSize 是以半磅爲單位的,若要設置文字爲 12 磅大小,需要設置 FontSize 爲 24
    在這裏插入圖片描述

設置段落屬性

爲這兩行字設置段落屬性,設置每行字的段前、段後各增加 4 磅間距。
段落屬性由 ParagraphProperties 表示,用於控制對齊方式、縮進、行距等。

titlePara.AppendChild(new ParagraphProperties(new SpacingBetweenLines { Before = "80", After = "80" }));
datePara.AppendChild(new ParagraphProperties(new SpacingBetweenLines { Before = "80", After = "80" }));

需要注意

  • ParagraphProperties 必須是 Paragraph 的第一個子元素,也就是說需要先添加 ParagraphProperties,後添加 Run
  • SpacingBetweenLines 是以二十分之一磅爲單位的,4 磅大小的值是 4 x 20 = 80
    在這裏插入圖片描述

輸出表格

表格由 TableTableRowTableCell 三級結構構成。那麼,向文檔中添加一個 9 行 x 6 列的表格:

Table weekTable = docBody.AppendChild(new Table());
for (int row = 0; row < 9; row++)
{
    TableRow tabRow = weekTable.AppendChild(new TableRow());
    for (int col = 0; col < 6; col++)
    {
        TableCell tabCell = tabRow.AppendChild(new TableCell());
    }
}

上面的代碼看着好像很對,但實際用 Word 打開生成的文檔會報錯。
如果要 Word 能夠正常打開文檔,每個 TableCell 至少需包含一個空段落纔行。

Paragraph tabCellPara = tabCell.AppendChild(new Paragraph());

表格邊框

上面的代碼雖然輸出了表格,但如果 Word 選項裏設置了不顯示段落標記的話,實際上這時候打開文檔,表格處是一片空白,什麼也看不到。
要想看到表格,還要設置表格邊框。表格邊框通過 TableProperties 設置。

TableProperties tabProps = new TableProperties(
    new TableBorders(
    new TopBorder
    {
        Val = new EnumValue<BorderValues>(BorderValues.Single),
        Size = 4,
        Color = "4F81BD"
    },
    new BottomBorder
    {
        Val = new EnumValue<BorderValues>(BorderValues.Single),
        Size = 4,
        Color = "4F81BD"
    },
    new LeftBorder
    {
        Val = new EnumValue<BorderValues>(BorderValues.Single),
        Size = 4,
        Color = "4F81BD"
    },
    new RightBorder
    {
        Val = new EnumValue<BorderValues>(BorderValues.Single),
        Size = 4,
        Color = "4F81BD"
    },
    new InsideHorizontalBorder
    {
        Val = new EnumValue<BorderValues>(BorderValues.Single),
        Size = 4,
        Color = "4F81BD"
    },
    new InsideVerticalBorder
    {
        Val = new EnumValue<BorderValues>(BorderValues.Single),
        Size = 4,
        Color = "4F81BD"
    }
    ));
weekTable.AppendChild(tabProps);

需要注意

  • TableProperties 必須是 Table 的第一個子元素,也就是說需要先添加 TableProperties,後添加 TableRow
  • BorderType.Size 是以八分之一磅爲單位的,Size = 4 表示邊框寬度是 4/8 = 0.5 磅

表格寬度

上面的代碼設置了表格邊框,可以看到表格了,但所有邊框緊挨在一起,看起來並不美觀,還需要設置表格寬度。
表格寬度由 TableWidth 表示,將 TableWidth 添加到 TableProperties 可設置表格寬度。
我們希望表格寬度充滿整個文檔,所以將以百分比設置表格寬度,並設爲 100%。

var tabWidth = new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct };
tabProps.AppendChild(tabWidth);

需要注意

  • TypeTableWidthUnitValues 枚舉類型,Pct 表示寬度值爲百分比,參考 TableWidthUnitValues Enum
  • TypePct 時,Width 是以 百分之一的五十分之一 爲單位的,所以要表示 100%,Width 值應該爲 100x50=5000
  • 另外,Width 值是字符型的(爲什麼要用字符型?後面會看到另一種用法)

行高

行高由 TableRowHeight 表示,通過將 TableRowHeight 添加到 TableRowProperties 來設置行高。

TableRowProperties tabRowProps = tabRow.AppendChild(new TableRowProperties(new TableRowHeight { Val = 600, HeightType = HeightRuleValues.Exact }));

需要注意

  • TableRowHeight 是以二十分之一磅爲單位的,600 表示行高是 600/20 = 30 磅

列寬

列寬可以通過設置單元格的寬度實現。
單元格寬度由 TableCellWidth 表示,通過將 TableCellWidth 添加到 TableCellProperties 來設置單元格寬度。
由於表格寬度已經使用百分比設置了,所以列寬也同樣使用百分比設置。

TableCellProperties tabCellProps;
if (col == 0)
{
    tabCellProps = tabCell.AppendChild(new TableCellProperties(new TableCellWidth { Width = "40%", Type = TableWidthUnitValues.Pct }));
}
else
{
    tabCellProps = tabCell.AppendChild(new TableCellProperties(new TableCellWidth { Width = "12%", Type = TableWidthUnitValues.Pct }));
}

需要注意

  • 上面設置表格寬度時,使用 5000 表示的 100%,這裏直接使用了百分數,是不是更簡單

單元格垂直對齊方式

單元格垂直對齊方式由 TableCellVerticalAlignment 表示,通過將 TableCellVerticalAlignment 添加到 TableCellProperties 來設置垂直對齊方式。
下面設置所有單元格內容垂直居中對齊:

tabCellProps.Append(new TableCellVerticalAlignment { Val = TableVerticalAlignmentValues.Center });

單元格水平對齊方式

單元格的垂直對齊是通過單元格屬性設置的,但單元格屬性裏卻找不到設置水平對齊的方法。
實際上,單元格水平對齊方式是通過單元格內文字的段落屬性設置的。
段落對齊方式由 Justification 表示,通過將 Justification 添加到 ParagraphProperties 來設置段落水平對齊方式。

ParagraphProperties tabCellParaProps = tabCellPara.AppendChild(new ParagraphProperties());
if (row == 0)
{
    tabCellParaProps.AppendChild(new Justification { Val = JustificationValues.Center });
}

單元格背景色

Shading 用於設置底紋和背景色,將 Shading 添加到 TableCellProperties 即可設置單元格背景色。

tabCellProps.Append(new Shading { Fill = "DBE5F1" });

單元格邊距

單元格邊距由 TableCellMargin 表示,通過將 TableCellMargin 添加到 TableCellProperties 來設置單元格邊距。

tabCellProps.AppendChild(new TableCellMargin(
    new LeftMargin { Width = "100", Type = TableWidthUnitValues.Dxa },
    new RightMargin { Width = "100", Type = TableWidthUnitValues.Dxa },
    new TopMargin { Width = "0", Type = TableWidthUnitValues.Dxa },
    new BottomMargin { Width = "0", Type = TableWidthUnitValues.Dxa }
));

需要注意

  • TableWidthUnitValues.Dxa 表示以二十分之一磅爲單位,所以這裏設置的單元格左右邊距是 100/20 = 5 磅

表格內容

每個單元格已經添加了空段落,再往空段落里加入 Run\Text 就能顯示文字了。

空單元格的文字字體、大小

對於空單元格,要設置文字字體和大小,正常思路是,向每個單元格的空段落添加一個 Run,並設置 RunProperties 的字體、大小。但經過實踐會發現,這樣做並不好用。
要想設置空單元格的文字字體和大小,需要把 RunProperties 添加到 ParagraphProperties 纔好用。但是奇怪的是,當向空段落添加 Run\Text,加入文字後,ParagraphProperties 裏的 RunProperties 對添加的文字並不起作用,需要另外給 Run 添加 RunProperties 才能對文字進行設置。不知道這是不是 Open XML SDK 的 Bug!

RunProperties tabCellParaRunProps = tabCellParaProps.AppendChild(new RunProperties());

RunFonts tabCellFonts = new RunFonts() { Ascii = "宋體", HighAnsi = "宋體", EastAsia = "宋體" };
FontSize tabCellFontSize = new FontSize() { Val = "24" };//設置字體大小,以半磅爲單位
tabCellParaRunProps.AppendChild(tabCellFonts);
tabCellParaRunProps.AppendChild(tabCellFontSize);

添加文字並設置字體、大小、顏色

var firstRowCells = weekTable.Descendants<TableRow>().First().Descendants<TableCell>();
for (int idx = 1; idx <= 5; idx++)
{
    Run tabCellRun = firstRowCells.ElementAt(idx).Elements<Paragraph>().First().AppendChild(new Run());
    RunProperties tabCellRunProps = tabCellRun.AppendChild(new RunProperties());
    RunFonts tabCellFonts = new RunFonts() { Ascii = "宋體", HighAnsi = "宋體", EastAsia = "宋體" };
    FontSize tabCellFontSize = new FontSize() { Val = "24" };//設置字體大小,以半磅爲單位
    DocumentFormat.OpenXml.Wordprocessing.Color tabCellFontColor = new DocumentFormat.OpenXml.Wordprocessing.Color() { Val = "365F91" };
    tabCellRunProps.AppendChild(tabCellFonts);
    tabCellRunProps.AppendChild(tabCellFontSize);
    tabCellRunProps.AppendChild(tabCellFontColor);

    string weekText = "星期";
    switch (idx)
    {
        case 1:
            weekText += "一";
            break;
        case 2:
            weekText += "二";
            break;
        case 3:
            weekText += "三";
            break;
        case 4:
            weekText += "四";
            break;
        case 5:
            weekText += "五";
            break;
        default:
            break;
    }
    tabCellRun.AppendChild(new Text(weekText));
}
var tabRows = weekTable.Descendants<TableRow>();
for (int idx = 1; idx <= 3; idx++)
{
    Run tabCellRun = tabRows.ElementAt(idx).Descendants<TableCell>().First().Elements<Paragraph>().First().AppendChild(new Run());
    RunProperties tabCellRunProps = tabCellRun.AppendChild(new RunProperties());
    RunFonts tabCellFonts = new RunFonts() { Ascii = "宋體", HighAnsi = "宋體", EastAsia = "宋體" };
    FontSize tabCellFontSize = new FontSize() { Val = "24" };//設置字體大小,以半磅爲單位
    tabCellRunProps.AppendChild(tabCellFonts);
    tabCellRunProps.AppendChild(tabCellFontSize);

    string itemText = "";
    if (idx == 1)
    {
        itemText = "家庭作業";
    }
    else if (idx == 2)
    {
        itemText = "扔掉垃圾和垃圾回收";
    }
    else if (idx == 3)
    {
        itemText = "洗碗碟";
    }
    tabCellRun.AppendChild(new Text(itemText));
}

輸出空行

要輸出一個空行,只要添加一個空段落就行了。

Paragraph emptyPara = docBody.AppendChild(new Paragraph());
emptyPara.AppendChild(new ParagraphProperties(new SpacingBetweenLines { Before = "80", After = "80" }));

輸出圖片

輸出圖片比較複雜,具體例子可以參考 如何:在字處理文檔中插入圖片

using A = DocumentFormat.OpenXml.Drawing;
using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
using PIC = DocumentFormat.OpenXml.Drawing.Pictures;
string picFileName = "clear.png";
ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Png);
using (FileStream stream = new FileStream(picFileName, FileMode.Open))
{
    imagePart.FeedData(stream);
}
AddImageToBody(wordDocument, mainPart.GetIdOfPart(imagePart));
private static void AddImageToBody(WordprocessingDocument wordDoc, string relationshipId)
{
    var element = new Drawing(
        new DW.Anchor(
            new DW.SimplePosition() { X = 0, Y = 0 },
            new DW.HorizontalPosition(
                new DW.PositionOffset("-9525")
                )
            { RelativeFrom = DW.HorizontalRelativePositionValues.Column },
            new DW.VerticalPosition() { RelativeFrom = DW.VerticalRelativePositionValues.Paragraph, PositionOffset = new DW.PositionOffset("360000") },
            new DW.Extent() { Cx = 8863330, Cy = 1763395 },
            new DW.WrapTopBottom(),
            new DW.DocProperties()
            {
                Id = 1U,
                Name = "Picture 1"
            },
            new DW.NonVisualGraphicFrameDrawingProperties(
                     new A.GraphicFrameLocks() { NoChangeAspect = true }
                     ),
            new A.Graphic(
                new A.GraphicData(
                    new PIC.Picture(
                        new PIC.NonVisualPictureProperties(
                                 new PIC.NonVisualDrawingProperties()
                                 {
                                     Id = 0U,
                                     Name = "New Bitmap Image.jpg"
                                 },
                                 new PIC.NonVisualPictureDrawingProperties()
                                 ),
                        new PIC.BlipFill(
                                 new A.Blip()
                                 {
                                     Embed = relationshipId
                                 },
                                 new A.Stretch(
                                     new A.FillRectangle()
                                     )
                                     ),
                             new PIC.ShapeProperties(
                                 new A.Transform2D(
                                     new A.Extents() { Cx = 8863330, Cy = 1763395 }
                                     ),
                                 new A.PresetGeometry() { Preset = A.ShapeTypeValues.Rectangle }
                                 )
                        )
                    )
                { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" }
                )
            )
        {
            SimplePos = false,
            RelativeHeight = 251658240U,
            BehindDoc = false,
            Locked = false,
            LayoutInCell = true,
            AllowOverlap = true }
        );

    wordDoc.MainDocumentPart.Document.Body.AppendChild(new Paragraph(new Run(element)));
}

需要注意

  • 圖片在文檔中有兩種佈局方式:內嵌浮動。示例 如何:在字處理文檔中插入圖片 使用的是內嵌佈局,而本文使用的是浮動佈局。內嵌佈局用 Inline 表示,浮動佈局用 Anchor 表示。
  • 圖片顯示的大小由 ShapeProperties.Transform2D.Extents 設置,Extents.CxExtents.Cy 分別表示寬和高。Extents.CxExtents.Cy 的單位是 EMU (English Metric Units,英語公制單位)。EMU 與釐米的換算關係如下:
    在這裏插入圖片描述

源碼下載

https://download.csdn.net/download/blackwoodcliff/11357951

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章