André Alves de Lima

Talking about Software Development and more…

Gerando arquivos do Word com C# e VB.NET

Uma das perguntas que encontramos frequentemente nos fóruns da MSDN é sobre a geração de arquivos do Office na plataforma de desenvolvimento do .NET Framework. Por ser uma funcionalidade muito específica, o .NET Framework não traz nativamente classes que possibilitam a manipulação de arquivos do Office. Nesse caso, a única chance que temos para gerarmos arquivos do Word com C# e VB.NET (ou outros tipos de arquivos da suíte do Office) é a utilização de bibliotecas externas.

O que muita gente não sabe é que existem inúmeras opções para trabalharmos com arquivos do Office no .NET. Neste artigo eu vou apresentar para você 4 opções para gerarmos arquivos do Word com C# e VB.NET. Como o assunto é bastante extenso, resolvi abordar somente arquivos do Word neste artigo. Em um próximo artigo eu mostro a mesma funcionalidade para arquivos do Excel.

Antes de abordarmos cada uma das opções, vamos construir um documento de exemplo. Pensando nas principais funcionalidades que utilizamos em arquivos do Word, preparei este documento:

Como você pode ver, nesse documento temos um parágrafo centralizado, outro parágrafo com uma fonte diferente da padrão, outro parágrafo com formatações diferentes dentro da mesma frase, uma tabela e uma imagem. Acho que ao conseguirmos construir um documento com essas funcionalidades, teremos uma boa ideia de como utilizamos cada uma das bibliotecas que serão apresentadas. Você pode baixar o documento de exemplo através deste link.

Além disso, para estudarmos cada uma das opções, vamos preparar um projeto do tipo “Windows Forms Application” com um formulário contendo 4 botões, um para cada biblioteca:

Opção 1: Office Automation

A primeira e mais simples opção que podemos utilizar para gerarmos arquivos do Word no C# é o Office Automation. Basicamente isso quer dizer que vamos utilizar as dlls de interoperabilidade (Interop) do Office para o .NET Framework. Essas bibliotecas possibilitam a operação das aplicações do Office através dos nossos aplicativos .NET.

O primeiro passo que temos que seguir nesse caso é adicionarmos a referência à dll de interoperabilidade do Word. Essa dll encontra-se dentro da categoria “Extensions” da tela “Reference Manager“:

Como você pode perceber, cada aplicativo do Office tem a sua própria dll de interop. Nesse caso adicionamos a biblioteca do Word, mas, se você fosse gerar um arquivo do Excel, você teria que, obviamente, adicionar a dll de interoperabilidade do Excel.

Com a dll adicionada, vamos dar um duplo clique no botão “Office Automation” para começarmos a implementar o código de geração do documento. A primeira coisa que temos que fazer é criarmos uma instância da classe “Word.Application“:

            // C#
            var wordApp = new Microsoft.Office.Interop.Word.Application();
            wordApp.Visible = false;
            var wordDoc = wordApp.Documents.Add();
        ' VB.NET
        Dim WordApp As New Microsoft.Office.Interop.Word.Application()
        WordApp.Visible = False
        Dim WordDoc = WordApp.Documents.Add()

O que estamos fazendo no código acima é simplesmente abrindo uma instância do Word, configurando-o para trabalhar em segundo plano (propriedade Visible = false) e criando um novo documento. Em seguida, temos que adicionar cada um dos parágrafos no nosso documento.

Como vimos no documento de exemplo, o primeiro parágrafo contém somente um texto simples com alinhamento centralizado. Veja como fica o código para gerar esse parágrafo:

            // C#
            var paragrafo1 = wordDoc.Content.Paragraphs.Add();
            paragrafo1.Range.Text = "Texto centralizado";
            paragrafo1.Range.ParagraphFormat.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphCenter;
            paragrafo1.Range.InsertParagraphAfter();
        ' VB.NET
        Dim Paragrafo1 = WordDoc.Content.Paragraphs.Add()
        Paragrafo1.Range.Text = "Texto centralizado"
        Paragrafo1.Range.ParagraphFormat.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphCenter
        Paragrafo1.Range.InsertParagraphAfter()

Fácil, não? Basta adicionarmos um parágrafo no documento, setarmos o texto do “Range” (que representa o conteúdo do parágrafo) e configurarmos o alinhamento para centralizado.

Em seguida, temos o parágrafo com fonte diferente. Adicionamos esse parágrafo da mesma forma que fizemos com o parágrafo anterior, porém, dessa vez alteraremos a propriedade “Font” do “Range“:

            // C#
            var paragrafo2 = wordDoc.Paragraphs.Add();
            paragrafo2.Range.Text = "Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18";
            paragrafo2.Range.Font.Name = "Arial";
            paragrafo2.Range.Font.Bold = 1;
            paragrafo2.Range.Font.Italic = 1;
            paragrafo2.Range.Font.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle;
            paragrafo2.Range.Font.Size = 18;
            paragrafo2.Range.InsertParagraphAfter();
        ' VB.NET
        Dim Paragrafo2 = WordDoc.Paragraphs.Add()
        Paragrafo2.Range.Text = "Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18"
        Paragrafo2.Range.Font.Name = "Arial"
        Paragrafo2.Range.Font.Bold = 1
        Paragrafo2.Range.Font.Italic = 1
        Paragrafo2.Range.Font.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle
        Paragrafo2.Range.Font.Size = 18
        Paragrafo2.Range.InsertParagraphAfter()

Feito isso, vamos ao parágrafo que contém a formatação mais complicada. Nesse próximo parágrafo configuraremos uma formatação diferente para algumas partes do texto. Essa é uma parte um pouco chata de fazermos com a dll de interop do Word. Como estamos praticamente operando o Word através da nossa aplicação, para conseguirmos aplicar uma formatação diferente para partes do texto, teremos que basicamente “digitar” o texto e selecionarmos cada uma das partes que queremos aplicar a formatação para alterarmos a propriedade desejada:

            // C#
            var paragrafo3 = wordDoc.Paragraphs.Add();
            paragrafo3.Range.Text = "Um pedaço da frase normal, outro pedaço negrito, outro sublinhado";
            paragrafo3.Range.Select();
            wordApp.Selection.ClearFormatting();
            wordApp.Selection.Collapse();
            var rangeNegrito = wordDoc.Range(paragrafo3.Range.Start + 27, paragrafo3.Range.Start + 47);
            rangeNegrito.Bold = 1;
            var rangeSublinhado = wordDoc.Range(paragrafo3.Range.Start + 49);
            rangeSublinhado.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle;
            paragrafo3.Range.InsertParagraphAfter();
        ' VB.NET
        Dim Paragrafo3 = WordDoc.Paragraphs.Add()
        Paragrafo3.Range.Text = "Um pedaço da frase normal, outro pedaço negrito, outro sublinhado"
        Paragrafo3.Range.Select()
        WordApp.Selection.ClearFormatting()
        WordApp.Selection.Collapse()
        Dim RangeNegrito = WordDoc.Range(Paragrafo3.Range.Start + 27, Paragrafo3.Range.Start + 47)
        RangeNegrito.Bold = 1
        Dim RangeSublinhado = WordDoc.Range(Paragrafo3.Range.Start + 49)
        RangeSublinhado.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle
        Paragrafo3.Range.InsertParagraphAfter()

Note que temos selecionar o texto utilizando o método “Range” do documento, passando os índices de início e final do texto que queremos aplicar a formatação. Não é o melhor dos códigos, mas, é a única maneira de aplicarmos formatações distintas no mesmo parágrafo com o Office Automation.

O próximo passo é criarmos mais um parágrafo, dessa vez contendo uma tabela com duas linhas e duas colunas:

            // C#
            var paragrafo4 = wordDoc.Paragraphs.Add();
            var tabela = wordDoc.Tables.Add(paragrafo4.Range, 2, 2);
            tabela.Rows[1].Cells[1].Range.Text = "Col1";
            tabela.Rows[1].Cells[2].Range.Text = "Col2";
            tabela.Rows[2].Cells[1].Range.Text = "A";
            tabela.Rows[2].Cells[2].Range.Text = "B";
            tabela.Borders.InsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle;
            tabela.Borders.OutsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle;
            tabela.Select();
            wordApp.Selection.ClearFormatting();
            wordApp.Selection.Collapse();
            paragrafo4.Range.InsertParagraphAfter();
        ' VB.NET
        Dim Paragrafo4 = WordDoc.Paragraphs.Add()
        Dim Tabela = WordDoc.Tables.Add(Paragrafo4.Range, 2, 2)
        Tabela.Rows(1).Cells(1).Range.Text = "Col1"
        Tabela.Rows(1).Cells(2).Range.Text = "Col2"
        Tabela.Rows(2).Cells(1).Range.Text = "A"
        Tabela.Rows(2).Cells(2).Range.Text = "B"
        Tabela.Borders.InsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle
        Tabela.Borders.OutsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle
        Tabela.Select()
        WordApp.Selection.ClearFormatting()
        WordApp.Selection.Collapse()
        Paragrafo4.Range.InsertParagraphAfter()

Tranquilo, não? Só temos que adicionar a tabela no documento (Tables.Add), passando o número de linhas e colunas. Depois, configuramos o conteúdo de cada uma das células e definimos o estilo de borda da tabela. Um único detalhe importante para quem utiliza o C# é que os índices das linhas e colunas começam no “1“, e não no “0” como estamos acostumados no C#.

Por fim, vamos ao último parágrafo, que deverá conter a imagem dos pinguins. É extremamente fácil adicionarmos uma imagem no documento com a dll de interoperabilidade do Word. Basta chamarmos o método AddPicture passando o caminho da imagem:

            // C#
            var paragrafo5 = wordDoc.Paragraphs.Add();
            paragrafo5.Range.InlineShapes.AddPicture(System.IO.Path.Combine(Application.StartupPath, "penguins.jpg"));
        ' VB.NET
        Dim Paragrafo5 = WordDoc.Paragraphs.Add()
        Paragrafo5.Range.InlineShapes.AddPicture(System.IO.Path.Combine(Application.StartupPath, "penguins.jpg"))

Só não esqueça de copiar o arquivo penguins.jpg para o diretório da aplicação.

O último passo que temos que seguir para fecharmos a geração do arquivo “doc” com as dlls de interop do Office é salvarmos o arquivo e fecharmos o Microsoft Word que estava rodando em segundo plano. Para isso, basta utilizarmos os métodos “SaveAs” e “Quit“. Nesse caso, você pode utilizar tanto o formato “doc” quanto o formato “docx“:

            // C#
            wordDoc.SaveAs2(System.IO.Path.Combine(Application.StartupPath, "documentoAutomation.docx"));
            wordApp.DisplayAlerts = Microsoft.Office.Interop.Word.WdAlertLevel.wdAlertsNone;
            wordApp.Quit();
        ' VB.NET
        WordDoc.SaveAs2(System.IO.Path.Combine(Application.StartupPath, "documentoAutomation.docx"))
        WordApp.DisplayAlerts = Microsoft.Office.Interop.Word.WdAlertLevel.wdAlertsNone
        WordApp.Quit()

Note que temos que suprimir os alertas do Microsoft Word, senão a aplicação rodando em segundo plano pode mostrar janelas de mensagens em alguns casos.

E com isso fechamos a geração do documento utilizando Office Automation (interop). Veja só o código completo do clique do botão “Office Automation“:

        // C#
        private void officeAutomationButton_Click(object sender, EventArgs e)
        {
            var wordApp = new Microsoft.Office.Interop.Word.Application();
            wordApp.Visible = false;
            var wordDoc = wordApp.Documents.Add();

            // Primeiro parágrafo (texto centralizado)
            var paragrafo1 = wordDoc.Content.Paragraphs.Add();
            paragrafo1.Range.Text = "Texto centralizado";
            paragrafo1.Range.ParagraphFormat.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphCenter;
            paragrafo1.Range.InsertParagraphAfter();

            // Segundo parágrafo (formatação diferente)
            var paragrafo2 = wordDoc.Paragraphs.Add();
            paragrafo2.Range.Text = "Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18";
            paragrafo2.Range.Font.Name = "Arial";
            paragrafo2.Range.Font.Bold = 1;
            paragrafo2.Range.Font.Italic = 1;
            paragrafo2.Range.Font.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle;
            paragrafo2.Range.Font.Size = 18;
            paragrafo2.Range.InsertParagraphAfter();

            // Terceiro parágrafo (multiplas formatações)
            var paragrafo3 = wordDoc.Paragraphs.Add();
            paragrafo3.Range.Text = "Um pedaço da frase normal, outro pedaço negrito, outro sublinhado";
            paragrafo3.Range.Select();
            wordApp.Selection.ClearFormatting();
            wordApp.Selection.Collapse();
            var rangeNegrito = wordDoc.Range(paragrafo3.Range.Start + 27, paragrafo3.Range.Start + 47);
            rangeNegrito.Bold = 1;
            var rangeSublinhado = wordDoc.Range(paragrafo3.Range.Start + 49);
            rangeSublinhado.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle;
            paragrafo3.Range.InsertParagraphAfter();

            // Quarto parágrafo (tabela)
            var paragrafo4 = wordDoc.Paragraphs.Add();
            var tabela = wordDoc.Tables.Add(paragrafo4.Range, 2, 2);
            tabela.Rows[1].Cells[1].Range.Text = "Col1";
            tabela.Rows[1].Cells[2].Range.Text = "Col2";
            tabela.Rows[2].Cells[1].Range.Text = "A";
            tabela.Rows[2].Cells[2].Range.Text = "B";
            tabela.Borders.InsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle;
            tabela.Borders.OutsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle;
            tabela.Select();
            wordApp.Selection.ClearFormatting();
            wordApp.Selection.Collapse();
            paragrafo4.Range.InsertParagraphAfter();

            // Quinto parágrafo (imagem)
            var paragrafo5 = wordDoc.Paragraphs.Add();
            paragrafo5.Range.InlineShapes.AddPicture(System.IO.Path.Combine(Application.StartupPath, "penguins.jpg"));

            wordDoc.SaveAs2(System.IO.Path.Combine(Application.StartupPath, "documentoAutomation.docx"));
            wordApp.DisplayAlerts = Microsoft.Office.Interop.Word.WdAlertLevel.wdAlertsNone;
            wordApp.Quit();
        }
    ' VB.NET
    Private Sub officeAutomationButton_Click(sender As Object, e As EventArgs) Handles officeAutomationButton.Click
        Dim WordApp As New Microsoft.Office.Interop.Word.Application()
        WordApp.Visible = False
        Dim WordDoc = WordApp.Documents.Add()

        ' Primeiro parágrafo (texto centralizado)
        Dim Paragrafo1 = WordDoc.Content.Paragraphs.Add()
        Paragrafo1.Range.Text = "Texto centralizado"
        Paragrafo1.Range.ParagraphFormat.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphCenter
        Paragrafo1.Range.InsertParagraphAfter()

        ' Segundo parágrafo (formatação diferente)
        Dim Paragrafo2 = WordDoc.Paragraphs.Add()
        Paragrafo2.Range.Text = "Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18"
        Paragrafo2.Range.Font.Name = "Arial"
        Paragrafo2.Range.Font.Bold = 1
        Paragrafo2.Range.Font.Italic = 1
        Paragrafo2.Range.Font.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle
        Paragrafo2.Range.Font.Size = 18
        Paragrafo2.Range.InsertParagraphAfter()

        ' Terceiro parágrafo (multiplas formatações)
        Dim Paragrafo3 = WordDoc.Paragraphs.Add()
        Paragrafo3.Range.Text = "Um pedaço da frase normal, outro pedaço negrito, outro sublinhado"
        Paragrafo3.Range.Select()
        WordApp.Selection.ClearFormatting()
        WordApp.Selection.Collapse()
        Dim RangeNegrito = WordDoc.Range(Paragrafo3.Range.Start + 27, Paragrafo3.Range.Start + 47)
        RangeNegrito.Bold = 1
        Dim RangeSublinhado = WordDoc.Range(Paragrafo3.Range.Start + 49)
        RangeSublinhado.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle
        Paragrafo3.Range.InsertParagraphAfter()

        ' Quarto parágrafo (tabela)
        Dim Paragrafo4 = WordDoc.Paragraphs.Add()
        Dim Tabela = WordDoc.Tables.Add(Paragrafo4.Range, 2, 2)
        Tabela.Rows(1).Cells(1).Range.Text = "Col1"
        Tabela.Rows(1).Cells(2).Range.Text = "Col2"
        Tabela.Rows(2).Cells(1).Range.Text = "A"
        Tabela.Rows(2).Cells(2).Range.Text = "B"
        Tabela.Borders.InsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle
        Tabela.Borders.OutsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle
        Tabela.Select()
        WordApp.Selection.ClearFormatting()
        WordApp.Selection.Collapse()
        Paragrafo4.Range.InsertParagraphAfter()

        ' Quinto parágrafo (imagem)
        Dim Paragrafo5 = WordDoc.Paragraphs.Add()
        Paragrafo5.Range.InlineShapes.AddPicture(System.IO.Path.Combine(Application.StartupPath, "penguins.jpg"))

        WordDoc.SaveAs2(System.IO.Path.Combine(Application.StartupPath, "documentoAutomation.docx"))
        WordApp.DisplayAlerts = Microsoft.Office.Interop.Word.WdAlertLevel.wdAlertsNone
        WordApp.Quit()
    End Sub

E aqui você confere o resultado:

Vale salientar que esse código só funcionará caso o Microsoft Word esteja instalado no computador onde a aplicação estiver sendo executada. Nas próximas alternativas nós veremos como gerar arquivos do Word sem que o Office esteja instalado no computador destino.

Opção 2: Aspose (biblioteca comercial)

A próxima alternativa que iremos analisar é a utilização da biblioteca comercial Aspose.Words. No momento da escrita desse artigo (maio de 2016), a licença dessa biblioteca está custando 999 dólares. Com essa biblioteca conseguimos gerar arquivos “doc” e “docx” sem que o Office esteja instalado no computador destino.

Para analisarmos a geração de arquivos com o Aspose, baixe o trial da biblioteca no site oficial. Uma vez baixada e descompactada, adicione a referência à dll “Aspose.Words.dll” no projeto da aplicação:

Com isso, podemos começar a desenvolver o código para a criação do arquivo “doc” com o Aspose. O primeiro passo é criarmos uma instância de Words.Document e Words.DocumentBuilder, além de configurarmos a fonte para “Calibri” no DocumentBuilder (o nosso documento de exemplo utiliza a fonte “Calibri“, mas, a fonte padrão do Aspose é a “Arial“):

            // C#
            var wordDoc = new Aspose.Words.Document();
            var builder = new Aspose.Words.DocumentBuilder(wordDoc);
            builder.Font.Name = "Calibri";
        ' VB.NET
        Dim WordDoc = New Aspose.Words.Document()
        Dim Builder = New Aspose.Words.DocumentBuilder(WordDoc)
        Builder.Font.Name = "Calibri"

Em seguida, vamos ver como é fácil criarmos o nosso primeiro parágrafo com texto centralizado, simplesmente configurando a propriedade ParagraphFormat.Alignment:

            // C#
            builder.ParagraphFormat.Alignment = Aspose.Words.ParagraphAlignment.Center;
            builder.Write("Texto centralizado");
        ' VB.NET
        Builder.ParagraphFormat.Alignment = Aspose.Words.ParagraphAlignment.Center
        Builder.Write("Texto centralizado")

Depois do parágrafo centralizado, temos o parágrafo com fonte diferente (Arial, Negrito, Itálico, Sublinhado, Tamanho 18). Para alterarmos a fonte do que está sendo gerado pelo DocumentBuilder, basta alterarmos as informações da propriedade “Font“:

            // C#
            var paragrafo2 = builder.InsertParagraph();
            builder.Font.Name = "Arial";
            builder.Font.Bold = true;
            builder.Font.Italic = true;
            builder.Font.Underline = Aspose.Words.Underline.Single;
            builder.Font.Size = 18;
            builder.Write("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18");
        ' VB.NET
        Dim Paragrafo2 = Builder.InsertParagraph()
        Builder.Font.Name = "Arial"
        Builder.Font.Bold = True
        Builder.Font.Italic = True
        Builder.Font.Underline = Aspose.Words.Underline.Single
        Builder.Font.Size = 18
        Builder.Write("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18")

O terceiro parágrafo é aquele que contém diferentes formatações dentro do mesmo parágrafo. Para conseguirmos diferentes formatações com o Aspose, temos que alternar entre configurações da propriedade “Font” e chamadas do método “Write“. Basicamente, o método “Write” respeitará as configurações de fonte que foram estabelecidas antes da sua chamada. Dessa forma, o código para a criação do parágrafo com formatações diferentes ficaria assim:

            // C#
            var paragrafo3 = builder.InsertParagraph();
            builder.ParagraphFormat.ClearFormatting();
            builder.Font.ClearFormatting();
            builder.Font.Name = "Calibri";
            builder.Write("Um pedaço da frase normal, ");
            builder.Font.Bold = true;
            builder.Write("outro pedaço negrito, ");
            builder.Font.Bold = false;
            builder.Font.Underline = Aspose.Words.Underline.Single;
            builder.Write("outro sublinhado");
        ' VB.NET
        Dim Paragrafo3 = Builder.InsertParagraph()
        Builder.ParagraphFormat.ClearFormatting()
        Builder.Font.ClearFormatting()
        Builder.Font.Name = "Calibri"
        Builder.Write("Um pedaço da frase normal, ")
        Builder.Font.Bold = True
        Builder.Write("outro pedaço negrito, ")
        Builder.Font.Bold = False
        Builder.Font.Underline = Aspose.Words.Underline.Single
        Builder.Write("outro sublinhado")

No quarto parágrafo teremos a tabela com duas colunas e duas linhas. No Aspose, tabelas são criadas através do método “StartTable” do DocumentBuilder. Uma vez criada a tabela, temos que adicionar cada uma das células utilizando o método “InsertCell“. As células são criadas no sentido esquerda para direita, cima para baixo. Uma vez que criamos todas as células de uma linha da tabela, temos que chamar o método “EndRow” para começarmos a próxima linha da tabela. Veja só como fica o código:

            // C#
            var paragrafo4 = builder.InsertParagraph();
            builder.Font.ClearFormatting();
            builder.Font.Name = "Calibri";
            builder.StartTable();
            builder.InsertCell();
            builder.Write("Col1");
            builder.InsertCell();
            builder.Write("Col2");
            builder.EndRow();
            builder.InsertCell();
            builder.Write("A");
            builder.InsertCell();
            builder.Write("B");
            builder.EndRow();
            builder.EndTable();
        ' VB.NET
        Dim Paragrafo4 = Builder.InsertParagraph()
        Builder.Font.ClearFormatting()
        Builder.Font.Name = "Calibri"
        Builder.StartTable()
        Builder.InsertCell()
        Builder.Write("Col1")
        Builder.InsertCell()
        Builder.Write("Col2")
        Builder.EndRow()
        Builder.InsertCell()
        Builder.Write("A")
        Builder.InsertCell()
        Builder.Write("B")
        Builder.EndRow()
        Builder.EndTable()

O quinto e último parágrafo deverá conter a imagem “penguins.jpg“. No caso da biblioteca Aspose, a inserção de imagens em documentos é extremamente simples. Só temos que chamar o método “InsertImage” do DocumentBuilder passando o caminho da imagem:

            // C#
            var paragrafo5 = builder.InsertParagraph();
            builder.InsertImage("penguins.jpg");
        ' VB.NET
        Dim Paragrafo5 = Builder.InsertParagraph()
        Builder.InsertImage("penguins.jpg")

Por fim, para salvarmos o arquivo, chamamos o método “Save” do documento. Podemos utilizar tanto o formato “doc” quanto o formato “docx“:

            // C#
            wordDoc.Save("documentoAspose.docx");
' VB.NET
WordDoc.Save("documentoAspose.docx")

Veja só como fica o código completo da geração do documento com a biblioteca Aspose:

        // C#
        private void asposeButton_Click(object sender, EventArgs e)
        {
            var wordDoc = new Aspose.Words.Document();
            var builder = new Aspose.Words.DocumentBuilder(wordDoc);
            builder.Font.Name = "Calibri";

            // Primeiro parágrafo (texto centralizado)
            builder.ParagraphFormat.Alignment = Aspose.Words.ParagraphAlignment.Center;
            builder.Write("Texto centralizado");

            // Segundo parágrafo (formatação diferente)
            var paragrafo2 = builder.InsertParagraph();
            builder.Font.Name = "Arial";
            builder.Font.Bold = true;
            builder.Font.Italic = true;
            builder.Font.Underline = Aspose.Words.Underline.Single;
            builder.Font.Size = 18;
            builder.Write("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18");

            // Terceiro parágrafo (multiplas formatações)
            var paragrafo3 = builder.InsertParagraph();
            builder.ParagraphFormat.ClearFormatting();
            builder.Font.ClearFormatting();
            builder.Font.Name = "Calibri";
            builder.Write("Um pedaço da frase normal, ");
            builder.Font.Bold = true;
            builder.Write("outro pedaço negrito, ");
            builder.Font.Bold = false;
            builder.Font.Underline = Aspose.Words.Underline.Single;
            builder.Write("outro sublinhado");

            // Quarto parágrafo (tabela)
            var paragrafo4 = builder.InsertParagraph();
            builder.Font.ClearFormatting();
            builder.Font.Name = "Calibri";
            builder.StartTable();
            builder.InsertCell();
            builder.Write("Col1");
            builder.InsertCell();
            builder.Write("Col2");
            builder.EndRow();
            builder.InsertCell();
            builder.Write("A");
            builder.InsertCell();
            builder.Write("B");
            builder.EndRow();
            builder.EndTable();

            // Quinto parágrafo (imagem)
            var paragrafo5 = builder.InsertParagraph();
            builder.InsertImage("penguins.jpg");

            wordDoc.Save("documentoAspose.docx");
        }
    ' VB.NET
    Private Sub asposeButton_Click(sender As Object, e As EventArgs) Handles asposeButton.Click
        Dim WordDoc = New Aspose.Words.Document()
        Dim Builder = New Aspose.Words.DocumentBuilder(WordDoc)
        Builder.Font.Name = "Calibri"

        ' Primeiro parágrafo (texto centralizado)
        Builder.ParagraphFormat.Alignment = Aspose.Words.ParagraphAlignment.Center
        Builder.Write("Texto centralizado")

        ' Segundo parágrafo (formatação diferente)
        Dim Paragrafo2 = Builder.InsertParagraph()
        Builder.Font.Name = "Arial"
        Builder.Font.Bold = True
        Builder.Font.Italic = True
        Builder.Font.Underline = Aspose.Words.Underline.Single
        Builder.Font.Size = 18
        Builder.Write("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18")

        ' Terceiro parágrafo (multiplas formatações)
        Dim Paragrafo3 = Builder.InsertParagraph()
        Builder.ParagraphFormat.ClearFormatting()
        Builder.Font.ClearFormatting()
        Builder.Font.Name = "Calibri"
        Builder.Write("Um pedaço da frase normal, ")
        Builder.Font.Bold = True
        Builder.Write("outro pedaço negrito, ")
        Builder.Font.Bold = False
        Builder.Font.Underline = Aspose.Words.Underline.Single
        Builder.Write("outro sublinhado")

        ' Quarto parágrafo (tabela)
        Dim Paragrafo4 = Builder.InsertParagraph()
        Builder.Font.ClearFormatting()
        Builder.Font.Name = "Calibri"
        Builder.StartTable()
        Builder.InsertCell()
        Builder.Write("Col1")
        Builder.InsertCell()
        Builder.Write("Col2")
        Builder.EndRow()
        Builder.InsertCell()
        Builder.Write("A")
        Builder.InsertCell()
        Builder.Write("B")
        Builder.EndRow()
        Builder.EndTable()

        ' Quinto parágrafo (imagem)
        Dim Paragrafo5 = Builder.InsertParagraph()
        Builder.InsertImage("penguins.jpg")

        WordDoc.Save("documentoAspose.docx")
    End Sub

E o resultado final:

Bem mais simples, não é mesmo? A vantagem de utilizarmos a biblioteca Aspose é que o código fica bem mais simples, porém, o custo com certeza pode ser um complicador.

Opção 3: OpenXML SDK

A próxima alternativa que analisaremos para gerarmos arquivos do Word com C# e VB.NET é o SDK do OpenXML. Essa é, sem dúvida alguma, a maneira mais difícil de gerarmos arquivos do Word nas nossas aplicações .NET.

Não sei se você sabe, mas, os arquivos “docx” são na realidade um tipo de arquivo XML. O que o SDK do OpenXML possibilita é a criação desse XML de forma mais “fácil” (entre aspas, porque mesmo assim o processo é bem doloroso).

Para analisarmos a criação do documento com o SDK do OpenXML, a primeira coisa que temos que fazer é baixar o SDK e compilar. Você pode baixar o projeto do OpenXML SDK no GitHub. Se você preferir, eu já baixei, compilei e estou disponibilizando a dll aqui (versão de maio de 2016 – extraia a dll de dentro do arquivo zip).

Uma vez adicionada a referência à dll, temos que criar quatro objetos: um WordprocessingDocument, um MainDocumentPart, um Wordprocessing.Document e um Wordprocessing.Body (já deu para sentir que o nível de dificuldade aumentou consideravelmente?):

            // C#
            var wordDoc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Create("documentoOpenXmlSdk.docx", DocumentFormat.OpenXml.WordprocessingDocumentType.Document);
            var mainPart = wordDoc.AddMainDocumentPart();
            var document = mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document();
            var body = document.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Body());
        ' VB.NET
        Dim WordDoc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Create("documentoOpenXmlSdk.docx", DocumentFormat.OpenXml.WordprocessingDocumentType.Document)
        Dim MainPart = WordDoc.AddMainDocumentPart()
        MainPart.Document = New DocumentFormat.OpenXml.Wordprocessing.Document()
        Dim Document = MainPart.Document
        Dim Body = Document.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Body())

Em seguida, vamos criar o primeiro parágrafo, com o texto centralizado:

            // C#
            var paragrafo1 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
            paragrafo1.ParagraphProperties = new DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties(new DocumentFormat.OpenXml.Wordprocessing.Justification() { Val = DocumentFormat.OpenXml.Wordprocessing.JustificationValues.Center });
            var run = paragrafo1.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
            run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Texto centralizado"));
        ' VB.NET
        Dim Paragrafo1 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
        Paragrafo1.ParagraphProperties = New DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties(New DocumentFormat.OpenXml.Wordprocessing.Justification() With {.Val = DocumentFormat.OpenXml.Wordprocessing.JustificationValues.Center})
        Dim Run = Paragrafo1.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
        Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Texto centralizado"))

Note que a estratégia que temos que seguir aqui é criarmos um parágrafo, configurar suas propriedades, atachá-lo no “Body” e adicionarmos um “Run” dentro do parágrafo. Os “Runs” no contexto do OpenXML SDK nada mais são do que “textos“.

Seguindo a mesma lógica, vamos ao segundo parágrafo, onde temos uma formatação mais customizada, contendo uma fonte diferente (Arial), negrito, itálico, sublinhado e com tamanho diferente:

            // C#
            var paragrafo2 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
            run = paragrafo2.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
            var runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.RunFonts() { Ascii = "Arial" }));
            runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Bold() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) });
            runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Italic() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) });
            runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Underline() { Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single });
            runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.FontSize() { Val = new DocumentFormat.OpenXml.StringValue("36") });
            run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18"));
        ' VB.NET
        Dim Paragrafo2 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
        Run = Paragrafo2.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
        Dim RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.RunFonts() With {.Ascii = "Arial"}))
        RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Bold() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)})
        RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Italic() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)})
        RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Underline() With {.Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single})
        RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.FontSize() With {.Val = New DocumentFormat.OpenXml.StringValue("36")})
        Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18"))

Um ponto muito importante a ser salientado com relação às fontes no OpenXML SDK é que elas são medidas em “meio ponto“. Ou seja, se quisermos configurar um texto com o tamanho “18” que estamos acostumados no Office, temos que, na verdade, configurar “36” no OpenXML SDK (multiplicar o valor desejado por dois).

O próximo parágrafo conterá o texto com formatações diferentes na mesma frase. Isso não é tão difícil de ser feito no OpenXML SDK. Nós só temos que criar “Runs” separadas para cada parte do texto, adicionando a formatação desejada em cada uma das partes:

            // C#
            var paragrafo3 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
            run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
            run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Um pedaço da frase normal, ") { Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve });
            run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
            runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.Bold() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) }));
            run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("outro pedaço negrito, ") { Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve });
            run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
            runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.Underline() { Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single }));
            run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("outro sublinhado"));
        ' VB.NET
        Dim Paragrafo3 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
        Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
        Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Um pedaço da frase normal, ") With {.Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve})
        Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
        RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.Bold() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)}))
        Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("outro pedaço negrito, ") With {.Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve})
        Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
        RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.Underline() With {.Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single}))
        Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("outro sublinhado"))

Nesse caso, uma observação importante é que as “Runs” do OpenXML SDK não preservam espaços no começo e final da frase. No nosso exemplo, como queremos manter o espaço entre os textos de formatações diferentes, temos que indicar a propriedade “Space = Preserve” na “Run“.

OK, até aqui tudo certo. O código foi consideravelmente maior e mais complexo do que as outras opções, mas, nada catastrófico. Agora, vamos ver o quão complexo é adicionar uma tabela com o OpenXML SDK? Primeiro eu vou mostrar o código e depois eu comento:

            // C#
            var table = new DocumentFormat.OpenXml.Wordprocessing.Table();
            var tableProperties = new DocumentFormat.OpenXml.Wordprocessing.TableProperties(
                new DocumentFormat.OpenXml.Wordprocessing.TableWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("5000") });
            tableProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.TableBorders(
            new DocumentFormat.OpenXml.Wordprocessing.TopBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            },
            new DocumentFormat.OpenXml.Wordprocessing.BottomBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            },
            new DocumentFormat.OpenXml.Wordprocessing.LeftBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            },
            new DocumentFormat.OpenXml.Wordprocessing.RightBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            },
            new DocumentFormat.OpenXml.Wordprocessing.InsideHorizontalBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            },
            new DocumentFormat.OpenXml.Wordprocessing.InsideVerticalBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            }));
            table.AppendChild(tableProperties);
            var tr = new DocumentFormat.OpenXml.Wordprocessing.TableRow();
            var tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
                new DocumentFormat.OpenXml.Wordprocessing.Run(
                    new DocumentFormat.OpenXml.Wordprocessing.Text("Col1"))));
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                    new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
            tr.Append(tc);
            tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
                new DocumentFormat.OpenXml.Wordprocessing.Run(
                    new DocumentFormat.OpenXml.Wordprocessing.Text("Col2"))));
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                    new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
            tr.Append(tc);
            table.Append(tr);
            tr = new DocumentFormat.OpenXml.Wordprocessing.TableRow();
            tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
                new DocumentFormat.OpenXml.Wordprocessing.Run(
                    new DocumentFormat.OpenXml.Wordprocessing.Text("A"))));
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                    new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
            tr.Append(tc);
            tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
                new DocumentFormat.OpenXml.Wordprocessing.Run(
                    new DocumentFormat.OpenXml.Wordprocessing.Text("B"))));
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                    new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
            tr.Append(tc);
            table.Append(tr);
            body.AppendChild(table);
        ' VB.NET
        Dim Table = New DocumentFormat.OpenXml.Wordprocessing.Table()
        Dim TableProperties = New DocumentFormat.OpenXml.Wordprocessing.TableProperties(
            New DocumentFormat.OpenXml.Wordprocessing.TableWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("5000")})
        TableProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.TableBorders(
            New DocumentFormat.OpenXml.Wordprocessing.TopBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            },
            New DocumentFormat.OpenXml.Wordprocessing.BottomBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            },
            New DocumentFormat.OpenXml.Wordprocessing.LeftBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            },
            New DocumentFormat.OpenXml.Wordprocessing.RightBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            },
            New DocumentFormat.OpenXml.Wordprocessing.InsideHorizontalBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            },
            New DocumentFormat.OpenXml.Wordprocessing.InsideVerticalBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            }))
        Table.AppendChild(TableProperties)
        Dim Tr = New DocumentFormat.OpenXml.Wordprocessing.TableRow()
        Dim Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
            New DocumentFormat.OpenXml.Wordprocessing.Run(
                New DocumentFormat.OpenXml.Wordprocessing.Text("Col1"))))
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
        Tr.Append(Tc)
        Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
            New DocumentFormat.OpenXml.Wordprocessing.Run(
                New DocumentFormat.OpenXml.Wordprocessing.Text("Col2"))))
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
        Tr.Append(Tc)
        Table.Append(Tr)
        Tr = New DocumentFormat.OpenXml.Wordprocessing.TableRow()
        Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
            New DocumentFormat.OpenXml.Wordprocessing.Run(
                New DocumentFormat.OpenXml.Wordprocessing.Text("A"))))
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
        Tr.Append(Tc)
        Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
            New DocumentFormat.OpenXml.Wordprocessing.Run(
                New DocumentFormat.OpenXml.Wordprocessing.Text("B"))))
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
        Tr.Append(Tc)
        Table.Append(Tr)
        Body.AppendChild(Table)

Uau! Essa doeu, hein! Primeiro temos um blocão de código para configurarmos as bordas da tabela (o padrão das tabelas no OpenXML SDK é sem bordas). Não existe uma propriedade de borda “geral“, ou seja, temos que configurar cada um dos tipos de borda (superior, inferior, direita, esquerda, interior horizontal e interior vertical). Depois, para cada linha da tabela temos que criar um TableRow. Para cada célula temos que criar um TableCell, um Paragraph e um Run. Se não bastasse isso, temos ainda que configurar a largura de cada uma das células (que no nosso caso, queremos 50% do tamanho da folha para cada coluna). E, para fechar com chave de ouro, o valor da largura percentual é configurado na medida de quinquagésimos de percentual! Ou seja, para 50%, temos que multiplicar 50×50 (daí o valor “2500“).

Você acha que depois dessa dificuldade para criar a tabela, o código não poderia ficar mais complicado, não é mesmo? Pois se você está de pé, é melhor se sentar. Eu vou mostrar agora o quão difícil é adicionar uma imagem no documento. Para você ter uma ideia, tem um artigo inteiro no MSDN só explicando isso.

Primeiro, precisamos de um método chamado “AddImageToBody“. Você pode pegar o código dele ali naquele artigo que eu linkei acima. Para facilitar a sua vida, eu vou colocar ele aqui também:

        // C#
        private static void AddImageToBody(DocumentFormat.OpenXml.Packaging.WordprocessingDocument wordDoc, string relationshipId)
        {
            // Define the reference of the image.
            var element =
                 new DocumentFormat.OpenXml.Wordprocessing.Drawing(
                     new DocumentFormat.OpenXml.Drawing.Wordprocessing.Inline(
                         new DocumentFormat.OpenXml.Drawing.Wordprocessing.Extent() { Cx = 990000L, Cy = 792000L },
                         new DocumentFormat.OpenXml.Drawing.Wordprocessing.EffectExtent()
                         {
                             LeftEdge = 0L,
                             TopEdge = 0L,
                             RightEdge = 0L,
                             BottomEdge = 0L
                         },
                         new DocumentFormat.OpenXml.Drawing.Wordprocessing.DocProperties()
                         {
                             Id = (DocumentFormat.OpenXml.UInt32Value)1U,
                             Name = "Picture 1"
                         },
                         new DocumentFormat.OpenXml.Drawing.Wordprocessing.NonVisualGraphicFrameDrawingProperties(
                             new DocumentFormat.OpenXml.Drawing.GraphicFrameLocks() { NoChangeAspect = true }),
                         new DocumentFormat.OpenXml.Drawing.Graphic(
                             new DocumentFormat.OpenXml.Drawing.GraphicData(
                                 new DocumentFormat.OpenXml.Drawing.Pictures.Picture(
                                     new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureProperties(
                                         new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualDrawingProperties()
                                         {
                                             Id = (DocumentFormat.OpenXml.UInt32Value)0U,
                                             Name = "New Bitmap Image.jpg"
                                         },
                                         new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureDrawingProperties()),
                                     new DocumentFormat.OpenXml.Drawing.Pictures.BlipFill(
                                         new DocumentFormat.OpenXml.Drawing.Blip(
                                             new DocumentFormat.OpenXml.Drawing.BlipExtensionList(
                                                 new DocumentFormat.OpenXml.Drawing.BlipExtension()
                                                 {
                                                     Uri =
                                                       "{28A0092B-C50C-407E-A947-70E740481C1C}"
                                                 })
                                         )
                                         {
                                             Embed = relationshipId,
                                             CompressionState =
                                             DocumentFormat.OpenXml.Drawing.BlipCompressionValues.Print
                                         },
                                         new DocumentFormat.OpenXml.Drawing.Stretch(
                                             new DocumentFormat.OpenXml.Drawing.FillRectangle())),
                                     new DocumentFormat.OpenXml.Drawing.Pictures.ShapeProperties(
                                         new DocumentFormat.OpenXml.Drawing.Transform2D(
                                             new DocumentFormat.OpenXml.Drawing.Offset() { X = 0L, Y = 0L },
                                             new DocumentFormat.OpenXml.Drawing.Extents() { Cx = 990000L, Cy = 792000L }),
                                         new DocumentFormat.OpenXml.Drawing.PresetGeometry(
                                             new DocumentFormat.OpenXml.Drawing.AdjustValueList()
                                         ) { Preset = DocumentFormat.OpenXml.Drawing.ShapeTypeValues.Rectangle }))
                             ) { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" })
                     )
                     {
                         DistanceFromTop = (DocumentFormat.OpenXml.UInt32Value)0U,
                         DistanceFromBottom = (DocumentFormat.OpenXml.UInt32Value)0U,
                         DistanceFromLeft = (DocumentFormat.OpenXml.UInt32Value)0U,
                         DistanceFromRight = (DocumentFormat.OpenXml.UInt32Value)0U,
                         EditId = "50D07946"
                     });

            // Append the reference to body, the element should be in a Run.
            wordDoc.MainDocumentPart.Document.Body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(new DocumentFormat.OpenXml.Wordprocessing.Run(element)));
        }
    ' VB.NET
    Private Shared Sub AddImageToBody(ByVal wordDoc As DocumentFormat.OpenXml.Packaging.WordprocessingDocument, ByVal relationshipId As String)
        ' Define the reference of the image.
        Dim element = New DocumentFormat.OpenXml.Wordprocessing.Drawing( _
                              New DocumentFormat.OpenXml.Drawing.Wordprocessing.Inline( _
                          New DocumentFormat.OpenXml.Drawing.Wordprocessing.Extent() With {.Cx = 990000L, .Cy = 792000L}, _
                          New DocumentFormat.OpenXml.Drawing.Wordprocessing.EffectExtent() With {.LeftEdge = 0L, .TopEdge = 0L, .RightEdge = 0L, .BottomEdge = 0L}, _
                          New DocumentFormat.OpenXml.Drawing.Wordprocessing.DocProperties() With {.Id = CType(1UI, DocumentFormat.OpenXml.UInt32Value), .Name = "Picture1"}, _
                          New DocumentFormat.OpenXml.Drawing.Wordprocessing.NonVisualGraphicFrameDrawingProperties( _
                              New DocumentFormat.OpenXml.Drawing.GraphicFrameLocks() With {.NoChangeAspect = True} _
                              ), _
                          New DocumentFormat.OpenXml.Drawing.Graphic(New DocumentFormat.OpenXml.Drawing.GraphicData( _
                                        New DocumentFormat.OpenXml.Wordprocessing.Picture( _
                                            New DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureProperties( _
                                                New DocumentFormat.OpenXml.Drawing.Pictures.NonVisualDrawingProperties() With {.Id = 0UI, .Name = "Koala.jpg"}, _
                                                New DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureDrawingProperties() _
                                                ), _
                                            New DocumentFormat.OpenXml.Drawing.Pictures.BlipFill( _
                                                New DocumentFormat.OpenXml.Drawing.Blip( _
                                                    New DocumentFormat.OpenXml.Drawing.BlipExtensionList( _
                                                        New DocumentFormat.OpenXml.Drawing.BlipExtension() With {.Uri = "{28A0092B-C50C-407E-A947-70E740481C1C}"}) _
                                                    ) With {.Embed = relationshipId, .CompressionState = DocumentFormat.OpenXml.Drawing.BlipCompressionValues.Print}, _
                                                New DocumentFormat.OpenXml.Drawing.Stretch( _
                                                    New DocumentFormat.OpenXml.Drawing.FillRectangle() _
                                                    ) _
                                                ), _
                                            New DocumentFormat.OpenXml.Drawing.Pictures.ShapeProperties( _
                                                New DocumentFormat.OpenXml.Drawing.Transform2D( _
                                                    New DocumentFormat.OpenXml.Drawing.Offset() With {.X = 0L, .Y = 0L}, _
                                                    New DocumentFormat.OpenXml.Drawing.Extents() With {.Cx = 990000L, .Cy = 792000L}), _
                                                New DocumentFormat.OpenXml.Drawing.PresetGeometry( _
                                                    New DocumentFormat.OpenXml.Drawing.AdjustValueList() _
                                                    ) With {.Preset = DocumentFormat.OpenXml.Drawing.ShapeTypeValues.Rectangle} _
                                                ) _
                                            ) _
                                        ) With {.Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture"} _
                                    ) _
                                ) With {.DistanceFromTop = 0UI, _
                                        .DistanceFromBottom = 0UI, _
                                        .DistanceFromLeft = 0UI, _
                                        .DistanceFromRight = 0UI} _
                            )

        ' Append the reference to body, the element should be in a Run.
        wordDoc.MainDocumentPart.Document.Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(New DocumentFormat.OpenXml.Wordprocessing.Run(element)))
    End Sub

Nem tente entender o que está sendo feito nesse código (eu não consegui). Apenas aceite que ele funciona.

Por fim, uma vez tendo esse método, fica fácil adicionarmos uma imagem no documento. São só essas 4 linhas de código (5 no caso do VB.NET):

            // C#
            var imagePart = mainPart.AddImagePart(DocumentFormat.OpenXml.Packaging.ImagePartType.Jpeg);
            using (var stream = new System.IO.FileStream("penguins.jpg", System.IO.FileMode.Open))
                imagePart.FeedData(stream);
            AddImageToBody(wordDoc, mainPart.GetIdOfPart(imagePart));
        ' VB.NET
        Dim ImagePart = MainPart.AddImagePart(DocumentFormat.OpenXml.Packaging.ImagePartType.Jpeg)
        Using Stream As New System.IO.FileStream("penguins.jpg", System.IO.FileMode.Open)
            ImagePart.FeedData(Stream)
        End Using
        AddImageToBody(WordDoc, MainPart.GetIdOfPart(ImagePart))

E com isso finalizamos a criação do nosso documento com o OpenXML SDK. Agora só temos que salvar e fechar o arquivo:

            // C#
            wordDoc.Save();
            wordDoc.Close();
        ' VB.NET
        WordDoc.Save()
        WordDoc.Close()

Se você se perdeu em um dos passos, não tem problema. Aqui vai o código completo do método para gerar o documento com o OpenXML SDK:

        // C#
        private void openXmlSdkButton_Click(object sender, EventArgs e)
        {
            var wordDoc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Create("documentoOpenXmlSdk.docx", DocumentFormat.OpenXml.WordprocessingDocumentType.Document);
            var mainPart = wordDoc.AddMainDocumentPart();
            var document = mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document();
            var body = document.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Body());

            // Primeiro parágrafo (texto centralizado)
            var paragrafo1 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
            paragrafo1.ParagraphProperties = new DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties(new DocumentFormat.OpenXml.Wordprocessing.Justification() { Val = DocumentFormat.OpenXml.Wordprocessing.JustificationValues.Center });
            var run = paragrafo1.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
            run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Texto centralizado"));

            // Segundo parágrafo (formatação diferente)
            var paragrafo2 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
            run = paragrafo2.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
            var runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.RunFonts() { Ascii = "Arial" }));
            runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Bold() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) });
            runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Italic() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) });
            runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Underline() { Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single });
            runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.FontSize() { Val = new DocumentFormat.OpenXml.StringValue("36") });
            run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18"));

            // Terceiro parágrafo (multiplas formatações)
            var paragrafo3 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
            run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
            run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Um pedaço da frase normal, ") { Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve });
            run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
            runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.Bold() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) }));
            run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("outro pedaço negrito, ") { Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve });
            run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
            runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.Underline() { Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single }));
            run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("outro sublinhado"));

            // Quarto parágrafo (tabela)
            var table = new DocumentFormat.OpenXml.Wordprocessing.Table();
            var tableProperties = new DocumentFormat.OpenXml.Wordprocessing.TableProperties(
                new DocumentFormat.OpenXml.Wordprocessing.TableWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("5000") });
            tableProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.TableBorders(
            new DocumentFormat.OpenXml.Wordprocessing.TopBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            },
            new DocumentFormat.OpenXml.Wordprocessing.BottomBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            },
            new DocumentFormat.OpenXml.Wordprocessing.LeftBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            },
            new DocumentFormat.OpenXml.Wordprocessing.RightBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            },
            new DocumentFormat.OpenXml.Wordprocessing.InsideHorizontalBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            },
            new DocumentFormat.OpenXml.Wordprocessing.InsideVerticalBorder
            {
                Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
            }));
            table.AppendChild(tableProperties);
            var tr = new DocumentFormat.OpenXml.Wordprocessing.TableRow();
            var tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
                new DocumentFormat.OpenXml.Wordprocessing.Run(
                    new DocumentFormat.OpenXml.Wordprocessing.Text("Col1"))));
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                    new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
            tr.Append(tc);
            tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
                new DocumentFormat.OpenXml.Wordprocessing.Run(
                    new DocumentFormat.OpenXml.Wordprocessing.Text("Col2"))));
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                    new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
            tr.Append(tc);
            table.Append(tr);
            tr = new DocumentFormat.OpenXml.Wordprocessing.TableRow();
            tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
                new DocumentFormat.OpenXml.Wordprocessing.Run(
                    new DocumentFormat.OpenXml.Wordprocessing.Text("A"))));
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                    new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
            tr.Append(tc);
            tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
                new DocumentFormat.OpenXml.Wordprocessing.Run(
                    new DocumentFormat.OpenXml.Wordprocessing.Text("B"))));
            tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                    new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
            tr.Append(tc);
            table.Append(tr);
            body.AppendChild(table);

            // Quinto parágrafo (imagem)
            var imagePart = mainPart.AddImagePart(DocumentFormat.OpenXml.Packaging.ImagePartType.Jpeg);
            using (var stream = new System.IO.FileStream("penguins.jpg", System.IO.FileMode.Open))
                imagePart.FeedData(stream);
            AddImageToBody(wordDoc, mainPart.GetIdOfPart(imagePart));

            wordDoc.Save();
            wordDoc.Close();
        }
    ' VB.NET
    Private Sub openXmlSdkButton_Click(sender As Object, e As EventArgs) Handles openXmlSdkButton.Click
        Dim WordDoc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Create("documentoOpenXmlSdk.docx", DocumentFormat.OpenXml.WordprocessingDocumentType.Document)
        Dim MainPart = WordDoc.AddMainDocumentPart()
        MainPart.Document = New DocumentFormat.OpenXml.Wordprocessing.Document()
        Dim Document = MainPart.Document
        Dim Body = Document.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Body())

        ' Primeiro parágrafo (texto centralizado)
        Dim Paragrafo1 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
        Paragrafo1.ParagraphProperties = New DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties(New DocumentFormat.OpenXml.Wordprocessing.Justification() With {.Val = DocumentFormat.OpenXml.Wordprocessing.JustificationValues.Center})
        Dim Run = Paragrafo1.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
        Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Texto centralizado"))

        ' Segundo parágrafo (formatação diferente)
        Dim Paragrafo2 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
        Run = Paragrafo2.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
        Dim RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.RunFonts() With {.Ascii = "Arial"}))
        RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Bold() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)})
        RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Italic() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)})
        RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Underline() With {.Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single})
        RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.FontSize() With {.Val = New DocumentFormat.OpenXml.StringValue("36")})
        Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18"))

        ' Terceiro parágrafo (multiplas formatações)
        Dim Paragrafo3 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
        Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
        Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Um pedaço da frase normal, ") With {.Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve})
        Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
        RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.Bold() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)}))
        Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("outro pedaço negrito, ") With {.Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve})
        Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
        RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.Underline() With {.Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single}))
        Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("outro sublinhado"))

        ' Quarto parágrafo (tabela)
        Dim Table = New DocumentFormat.OpenXml.Wordprocessing.Table()
        Dim TableProperties = New DocumentFormat.OpenXml.Wordprocessing.TableProperties(
            New DocumentFormat.OpenXml.Wordprocessing.TableWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("5000")})
        TableProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.TableBorders(
            New DocumentFormat.OpenXml.Wordprocessing.TopBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            },
            New DocumentFormat.OpenXml.Wordprocessing.BottomBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            },
            New DocumentFormat.OpenXml.Wordprocessing.LeftBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            },
            New DocumentFormat.OpenXml.Wordprocessing.RightBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            },
            New DocumentFormat.OpenXml.Wordprocessing.InsideHorizontalBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            },
            New DocumentFormat.OpenXml.Wordprocessing.InsideVerticalBorder With
            {
                .Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
            }))
        Table.AppendChild(TableProperties)
        Dim Tr = New DocumentFormat.OpenXml.Wordprocessing.TableRow()
        Dim Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
            New DocumentFormat.OpenXml.Wordprocessing.Run(
                New DocumentFormat.OpenXml.Wordprocessing.Text("Col1"))))
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
        Tr.Append(Tc)
        Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
            New DocumentFormat.OpenXml.Wordprocessing.Run(
                New DocumentFormat.OpenXml.Wordprocessing.Text("Col2"))))
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
        Tr.Append(Tc)
        Table.Append(Tr)
        Tr = New DocumentFormat.OpenXml.Wordprocessing.TableRow()
        Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
            New DocumentFormat.OpenXml.Wordprocessing.Run(
                New DocumentFormat.OpenXml.Wordprocessing.Text("A"))))
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
        Tr.Append(Tc)
        Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
            New DocumentFormat.OpenXml.Wordprocessing.Run(
                New DocumentFormat.OpenXml.Wordprocessing.Text("B"))))
        Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
        Tr.Append(Tc)
        Table.Append(Tr)
        Body.AppendChild(Table)

        ' Quinto parágrafo (imagem)
        Dim ImagePart = MainPart.AddImagePart(DocumentFormat.OpenXml.Packaging.ImagePartType.Jpeg)
        Using Stream As New System.IO.FileStream("penguins.jpg", System.IO.FileMode.Open)
            ImagePart.FeedData(Stream)
        End Using
        AddImageToBody(WordDoc, MainPart.GetIdOfPart(ImagePart))

        WordDoc.Save()
        WordDoc.Close()
    End Sub

E aqui vai uma imagem do resultado obtido (note que mesmo com essa parafernalha toda, não consegui adicionar a imagem de forma que ela ocupasse a largura toda da página):

Opção 4: Biblioteca DocX

Agora que já estamos recuperados do susto que tomamos com o OpenXML SDK, quero apresentar para você a biblioteca DocX. Essa biblioteca é bem levinha e implementa basicamente a mesma coisa que o OpenXML SDK, só que de uma maneira muito mais fácil de ser utilizada. No final das contas, ela gera o XML correspondente ao DOCX e também não necessita da instalação do Office no computador cliente.

Para adicionar essa biblioteca no nosso projeto, procure por “docx” no NuGet e instale o pacote:

Nota: se você não sabe como adicionar pacotes do NuGet, confira o artigo que eu escrevi uns tempos atrás mostrando como gerenciar pacotes do NuGet no Visual Studio.

Uma vez adicionado o pacote no projeto, nós conseguiremos utilizar as suas funcionalidades, que se encontram no namespace “Novacode“. O primeiro passo é criarmos um bloco “using” chamando o método DocX.Create. Todo o código desse exemplo deverá ser colocado dentro desse bloco “using“:

            // C#
            using (var docX = Novacode.DocX.Create("documentoDocX.docx"))
            {
            }
        ' VB.NET
        Using DocX = Novacode.DocX.Create("documentoDocX.docx")

        End Using

Uma vez criado o bloco “using” vamos adicionar o código para criarmos o primeiro parágrafo, que deve ser centralizado:

                // C#
                var paragrafo1 = docX.InsertParagraph();
                paragrafo1.LineSpacingAfter = 8;
                paragrafo1.Append("Texto centralizado");
                paragrafo1.Alignment = Novacode.Alignment.center;
            ' VB.NET
            Dim Paragrafo1 = DocX.InsertParagraph()
            Paragrafo1.LineSpacingAfter = 8
            Paragrafo1.Append("Texto centralizado")
            Paragrafo1.Alignment = Novacode.Alignment.center

Note que não tem segredo nenhum nesse caso. Basta inserirmos o parágrafo no documento, adicionarmos o texto dentro dele (método Append) e configurarmos o alinhamento do parágrafo através da propriedade Alignment. Um detalhe importante que temos que prestar atenção com a biblioteca DocX é que o espaçamento padrão dos parágrafos não é o que estamos acostumados com o Word. Portanto, sempre que criamos um parágrafo, temos que configurar o espaçamento para “8“.

Agora vamos continuar com o próximo parágrafo, que tem uma formatação um pouco mais elaborada:

                // C#
                var paragrafo2 = docX.InsertParagraph();
                paragrafo2.LineSpacingAfter = 8;
                paragrafo2.Append("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18");
                paragrafo2.Font(new FontFamily("Arial"));
                paragrafo2.FontSize(18);
                paragrafo2.Bold();
                paragrafo2.Italic();
                paragrafo2.UnderlineStyle(Novacode.UnderlineStyle.singleLine);
            ' VB.NET
            Dim Paragrafo2 = DocX.InsertParagraph()
            Paragrafo2.LineSpacingAfter = 8
            Paragrafo2.Append("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18")
            Paragrafo2.Font(New FontFamily("Arial"))
            Paragrafo2.FontSize(18)
            Paragrafo2.Bold()
            Paragrafo2.Italic()
            Paragrafo2.UnderlineStyle(Novacode.UnderlineStyle.singleLine)

Veja que as formatações na biblioteca DocX são feitas através de métodos. Ou seja, para fazer com que um parágrafo fique em negrito, temos que chamar o método Bold nesse parágrafo. O mesmo vale para o itálico, sublinhado e configurações de fonte.

E como é que fica o parágrafo com formatações diferentes dentro da mesma frase? Confira:

                // C#
                var paragrafo3 = docX.InsertParagraph();
                paragrafo3.LineSpacingAfter = 8;
                paragrafo3.Append("Um pedaço da frase normal, ");
                var negrito = paragrafo3.Append("outro pedaço negrito, ");
                negrito.Bold();
                var sublinhado = paragrafo3.Append("outro sublinhado");
                sublinhado.UnderlineStyle(Novacode.UnderlineStyle.singleLine);
            ' VB.NET
            Dim Paragrafo3 = DocX.InsertParagraph()
            Paragrafo3.LineSpacingAfter = 8
            Paragrafo3.Append("Um pedaço da frase normal, ")
            Dim Negrito = Paragrafo3.Append("outro pedaço negrito, ")
            Negrito.Bold()
            Dim Sublinhado = Paragrafo3.Append("outro sublinhado")
            Sublinhado.UnderlineStyle(Novacode.UnderlineStyle.singleLine)

Observe que, quando chamamos o método Append para adicionar um texto no parágrafo, ele retorna uma variável com a parte do parágrafo que acabamos de adicionar. Dessa forma, para deixarmos somente aquela parte do parágrafo em negrito, basta chamarmos o método Bold na variável retornada.

Em seguida, vamos à tabela:

                // C#
                var paragrafo4 = docX.InsertParagraph();
                paragrafo4.LineSpacingAfter = 8;
                var tabela = paragrafo4.InsertTableAfterSelf(2, 2);
                tabela.AutoFit = Novacode.AutoFit.Window;
                tabela.Rows[0].Cells[0].InsertParagraph("Col1");
                tabela.Rows[0].Cells[1].InsertParagraph("Col2");
                tabela.Rows[1].Cells[0].InsertParagraph("A");
                tabela.Rows[1].Cells[1].InsertParagraph("B");
                tabela.SetBorder(Novacode.TableBorderType.Top, new Novacode.Border());
                tabela.SetBorder(Novacode.TableBorderType.Bottom, new Novacode.Border());
                tabela.SetBorder(Novacode.TableBorderType.Left, new Novacode.Border());
                tabela.SetBorder(Novacode.TableBorderType.Right, new Novacode.Border());
                tabela.SetBorder(Novacode.TableBorderType.InsideH, new Novacode.Border());
                tabela.SetBorder(Novacode.TableBorderType.InsideV, new Novacode.Border());
            ' VB.NET
            Dim Paragrafo4 = DocX.InsertParagraph()
            Paragrafo4.LineSpacingAfter = 8
            Dim Tabela = Paragrafo4.InsertTableAfterSelf(2, 2)
            Tabela.AutoFit = Novacode.AutoFit.Window
            Tabela.Rows(0).Cells(0).InsertParagraph("Col1")
            Tabela.Rows(0).Cells(1).InsertParagraph("Col2")
            Tabela.Rows(1).Cells(0).InsertParagraph("A")
            Tabela.Rows(1).Cells(1).InsertParagraph("B")
            Tabela.SetBorder(Novacode.TableBorderType.Top, New Novacode.Border())
            Tabela.SetBorder(Novacode.TableBorderType.Bottom, New Novacode.Border())
            Tabela.SetBorder(Novacode.TableBorderType.Left, New Novacode.Border())
            Tabela.SetBorder(Novacode.TableBorderType.Right, New Novacode.Border())
            Tabela.SetBorder(Novacode.TableBorderType.InsideH, New Novacode.Border())
            Tabela.SetBorder(Novacode.TableBorderType.InsideV, New Novacode.Border())

Como podemos observar no código acima, é muito fácil e intuitivo criarmos uma tabela com a biblioteca DocX.

Por fim, vamos adicionar a imagem no documento. Os passos para adicionarmos uma imagem com a biblioteca DocX são: 1) incluir a imagem no documento (método AddImage passando um MemoryStream com a representação do arquivo contendo a imagem), 2) chamar o método CreatePicture na imagem do documento e guardar o retorno em uma variável, 3) passar a imagem retornada pelo método CreatePicture como parâmetro para o método InsertPicture:

                // C#
                using (var memoryStream = new System.IO.MemoryStream())
                {
                    var paragrafo5 = docX.InsertParagraph();
                    paragrafo5.LineSpacingAfter = 8;

                    var imagemDoArquivo = Image.FromFile("penguins.jpg");
                    imagemDoArquivo.Save(memoryStream, imagemDoArquivo.RawFormat);
                    memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
                    var imagemNoDocumento = docX.AddImage(memoryStream);
                    var picture = imagemNoDocumento.CreatePicture();
                    var maxWidth = Convert.ToInt32(docX.PageWidth - docX.MarginLeft - docX.MarginRight);
                    if (picture.Width > maxWidth)
                    {
                        var ratio = (double)maxWidth / (double)picture.Width;
                        picture.Width = maxWidth;
                        picture.Height = Convert.ToInt32(picture.Height * ratio);
                    }
                    
                    paragrafo5.InsertPicture(picture);
                }
            ' VB.NET
            Using MemoryStream As New System.IO.MemoryStream()
                Dim Paragrafo5 = DocX.InsertParagraph()
                Paragrafo5.LineSpacingAfter = 8

                Dim ImagemDoArquivo = Image.FromFile("penguins.jpg")
                ImagemDoArquivo.Save(MemoryStream, ImagemDoArquivo.RawFormat)
                MemoryStream.Seek(0, System.IO.SeekOrigin.Begin)
                Dim ImagemNoDocumento = DocX.AddImage(MemoryStream)
                Dim Picture = ImagemNoDocumento.CreatePicture()
                Dim MaxWidth = Convert.ToInt32(DocX.PageWidth - DocX.MarginLeft - DocX.MarginRight)
                If (Picture.Width > MaxWidth) Then
                    Dim Ratio = CDbl(MaxWidth) / CDbl(Picture.Width)
                    Picture.Width = MaxWidth
                    Picture.Height = Convert.ToInt32(Picture.Height * Ratio)
                End If

                Paragrafo5.InsertPicture(Picture)
            End Using

A única coisa que fiz a mais no bloco de código acima foi redimensionar a imagem para que ela ocupasse o tamanho todo da página (desconsiderando as margens). Como a biblioteca DocX adiciona a imagem no tamanho original, infelizmente temos que fazer esse ajuste manualmente.

Finalmente, vamos salvar o arquivo chamando o método Save:

                // C#
                docX.Save();
            ' VB.NET
            DocX.Save()

Veja o código completo para a geração do documento com a biblioteca DocX:

        // C#
        private void docXButton_Click(object sender, EventArgs e)
        {
            using (var docX = Novacode.DocX.Create("documentoDocX.docx"))
            {
                // Primeiro parágrafo (texto centralizado)
                var paragrafo1 = docX.InsertParagraph();
                paragrafo1.LineSpacingAfter = 8;
                paragrafo1.Append("Texto centralizado");
                paragrafo1.Alignment = Novacode.Alignment.center;

                // Segundo parágrafo (formatação diferente)
                var paragrafo2 = docX.InsertParagraph();
                paragrafo2.LineSpacingAfter = 8;
                paragrafo2.Append("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18");
                paragrafo2.Font(new FontFamily("Arial"));
                paragrafo2.FontSize(18);
                paragrafo2.Bold();
                paragrafo2.Italic();
                paragrafo2.UnderlineStyle(Novacode.UnderlineStyle.singleLine);

                // Terceiro parágrafo (multiplas formatações)
                var paragrafo3 = docX.InsertParagraph();
                paragrafo3.LineSpacingAfter = 8;
                paragrafo3.Append("Um pedaço da frase normal, ");
                var negrito = paragrafo3.Append("outro pedaço negrito, ");
                negrito.Bold();
                var sublinhado = paragrafo3.Append("outro sublinhado");
                sublinhado.UnderlineStyle(Novacode.UnderlineStyle.singleLine);

                // Quarto parágrafo (tabela)
                var paragrafo4 = docX.InsertParagraph();
                paragrafo4.LineSpacingAfter = 8;
                var tabela = paragrafo4.InsertTableAfterSelf(2, 2);
                tabela.AutoFit = Novacode.AutoFit.Window;
                tabela.Rows[0].Cells[0].InsertParagraph("Col1");
                tabela.Rows[0].Cells[1].InsertParagraph("Col2");
                tabela.Rows[1].Cells[0].InsertParagraph("A");
                tabela.Rows[1].Cells[1].InsertParagraph("B");
                tabela.SetBorder(Novacode.TableBorderType.Top, new Novacode.Border());
                tabela.SetBorder(Novacode.TableBorderType.Bottom, new Novacode.Border());
                tabela.SetBorder(Novacode.TableBorderType.Left, new Novacode.Border());
                tabela.SetBorder(Novacode.TableBorderType.Right, new Novacode.Border());
                tabela.SetBorder(Novacode.TableBorderType.InsideH, new Novacode.Border());
                tabela.SetBorder(Novacode.TableBorderType.InsideV, new Novacode.Border());

                // Quinto parágrafo (imagem)
                using (var memoryStream = new System.IO.MemoryStream())
                {
                    var paragrafo5 = docX.InsertParagraph();
                    paragrafo5.LineSpacingAfter = 8;

                    var imagemDoArquivo = Image.FromFile("penguins.jpg");
                    imagemDoArquivo.Save(memoryStream, imagemDoArquivo.RawFormat);
                    memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
                    var imagemNoDocumento = docX.AddImage(memoryStream);
                    var picture = imagemNoDocumento.CreatePicture();
                    var maxWidth = Convert.ToInt32(docX.PageWidth - docX.MarginLeft - docX.MarginRight);
                    if (picture.Width > maxWidth)
                    {
                        var ratio = (double)maxWidth / (double)picture.Width;
                        picture.Width = maxWidth;
                        picture.Height = Convert.ToInt32(picture.Height * ratio);
                    }
                    
                    paragrafo5.InsertPicture(picture);
                }

                docX.Save();
            }
        }
    ' VB.NET
    Private Sub docXButton_Click(sender As Object, e As EventArgs) Handles docXButton.Click
        Using DocX = Novacode.DocX.Create("documentoDocX.docx")
            ' Primeiro parágrafo (texto centralizado)
            Dim Paragrafo1 = DocX.InsertParagraph()
            Paragrafo1.LineSpacingAfter = 8
            Paragrafo1.Append("Texto centralizado")
            Paragrafo1.Alignment = Novacode.Alignment.center

            ' Segundo parágrafo (formatação diferente)
            Dim Paragrafo2 = DocX.InsertParagraph()
            Paragrafo2.LineSpacingAfter = 8
            Paragrafo2.Append("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18")
            Paragrafo2.Font(New FontFamily("Arial"))
            Paragrafo2.FontSize(18)
            Paragrafo2.Bold()
            Paragrafo2.Italic()
            Paragrafo2.UnderlineStyle(Novacode.UnderlineStyle.singleLine)

            ' Terceiro parágrafo (multiplas formatações)
            Dim Paragrafo3 = DocX.InsertParagraph()
            Paragrafo3.LineSpacingAfter = 8
            Paragrafo3.Append("Um pedaço da frase normal, ")
            Dim Negrito = Paragrafo3.Append("outro pedaço negrito, ")
            Negrito.Bold()
            Dim Sublinhado = Paragrafo3.Append("outro sublinhado")
            Sublinhado.UnderlineStyle(Novacode.UnderlineStyle.singleLine)

            ' Quarto parágrafo (tabela)
            Dim Paragrafo4 = DocX.InsertParagraph()
            Paragrafo4.LineSpacingAfter = 8
            Dim Tabela = Paragrafo4.InsertTableAfterSelf(2, 2)
            Tabela.AutoFit = Novacode.AutoFit.Window
            Tabela.Rows(0).Cells(0).InsertParagraph("Col1")
            Tabela.Rows(0).Cells(1).InsertParagraph("Col2")
            Tabela.Rows(1).Cells(0).InsertParagraph("A")
            Tabela.Rows(1).Cells(1).InsertParagraph("B")
            Tabela.SetBorder(Novacode.TableBorderType.Top, New Novacode.Border())
            Tabela.SetBorder(Novacode.TableBorderType.Bottom, New Novacode.Border())
            Tabela.SetBorder(Novacode.TableBorderType.Left, New Novacode.Border())
            Tabela.SetBorder(Novacode.TableBorderType.Right, New Novacode.Border())
            Tabela.SetBorder(Novacode.TableBorderType.InsideH, New Novacode.Border())
            Tabela.SetBorder(Novacode.TableBorderType.InsideV, New Novacode.Border())

            ' Quinto parágrafo (imagem)
            Using MemoryStream As New System.IO.MemoryStream()
                Dim Paragrafo5 = DocX.InsertParagraph()
                Paragrafo5.LineSpacingAfter = 8

                Dim ImagemDoArquivo = Image.FromFile("penguins.jpg")
                ImagemDoArquivo.Save(MemoryStream, ImagemDoArquivo.RawFormat)
                MemoryStream.Seek(0, System.IO.SeekOrigin.Begin)
                Dim ImagemNoDocumento = DocX.AddImage(MemoryStream)
                Dim Picture = ImagemNoDocumento.CreatePicture()
                Dim MaxWidth = Convert.ToInt32(DocX.PageWidth - DocX.MarginLeft - DocX.MarginRight)
                If (Picture.Width > MaxWidth) Then
                    Dim Ratio = CDbl(MaxWidth) / CDbl(Picture.Width)
                    Picture.Width = MaxWidth
                    Picture.Height = Convert.ToInt32(Picture.Height * Ratio)
                End If

                Paragrafo5.InsertPicture(Picture)
            End Using

            DocX.Save()
        End Using
    End Sub

E o resultado obtido:

Concluindo: qual opção eu devo escolher?

Muito bem, agora que já vimos os detalhes de cada uma dessas quatro opções que temos para gerar arquivos do Word com C# e VB.NET, qual opção devemos escolher? Qual dessas opções é a melhor? Bom, como tudo na vida, a resposta para essa pergunta depende do cenário da sua aplicação.

Cenário 1: arquivo “doc“, Office instalado

Se você obrigatoriamente precisa gerar um arquivo “doc” (formato do Office 97 e inferior) e você tem certeza que o Office estará instalado no computador cliente, a primeira opção (Office Automation / Office Interop) é a mais recomendada. A não ser que a sua empresa esteja disposta a gastar 1000 dólares com uma licença do Aspose.

Cenário 2: arquivo “doc“, Office não instalado

Por outro lado, se você obrigatoriamente precisa gerar um arquivo “doc” e não tiver certeza que o Office estará instalado no computador cliente, a única alternativa é partir para alguma biblioteca comercial. Dentre as que eu testei rapidamente, a Aspose foi a melhor que eu encontrei (além de ser a mais conhecida). Pode ser que você encontre alguma opção mais barata e que também atenda às suas necessidades. Se você encontrar alguma que tenha um custo x benefício melhor, avisa nos comentários.

Cenário 3: arquivo “docx

Por fim, se você não tiver que obrigatoriamente gerar um arquivo “doc“, ou seja, se o tipo de arquivo “docx” for o suficiente, recomendo que você utilize a biblioteca DocX. Ela é gratuita, open source e implementa a maioria das funcionalidades existentes no Word.

Dentre as opções apresentadas nesse artigo, a única que eu realmente não recomendo é o OpenXML SDK. Apesar de ser extremamente completo (com todas as funcionalidades possíveis do formato “docx“), ele é muito difícil de ser utilizado, o que acaba não compensando a sua utilização se compararmos com a biblioteca DocX.

Enfim, espero que com o estudo apresentado nesse artigo você tenha aprendido de uma vez por todas a gerar arquivos do Word com C# e VB.NET. E você, já teve que fazer algo parecido? Qual dessas opções você utilizou? Conte-nos mais detalhes ali nos comentários.

Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

55 thoughts on “Gerando arquivos do Word com C# e VB.NET

  • Italo Ramon disse:

    Muito bom, parabéns …

  • Ailton disse:

    Muito bom o seu artigo André, era uma necessidade que eu tinha para gerar algumas informações do banco de dados para um arquivo .docx em forma de relatório. Parabéns pelos artigos.

  • Murilo Fujita disse:

    André, peço uma orientação, por favor. Eu instalei Microsoft Office 2010: Primary Interop Assemblies Redistributable entre outros download e nada de conseguir a biblioteca para criar documentos do Word (microsoft.office.word.interop) – a primeira opção do seu texto. O que é necessário? Obrigado.

    • andrealveslima disse:

      Olá Murilo!

      Como mencionei no artigo, a primeira opção (Office Automation) só funciona se você tiver o Microsoft Office instalado (pelo menos no Microsoft Word) no computador onde você estiver rodando a aplicação (e, obviamente, no computador de desenvolvimento também para você conseguir debugar a aplicação).. Não adianta instalar os redistributables, tem que ter o Word instalado..

      O Word está instalado no computador onde você está tentando rodar a aplicação?

      Abraço!
      André Lima

  • Maier disse:

    Ola Andre, parabéns pela matéria.

    Estou tendo dificuldade em incluir marca d´agua no documento criado, como posso fazer a inclusão de um texto?

    Abraço!

    Maier

    • andrealveslima disse:

      Olá Maier!

      Qual das quatro opções de geração de arquivos do Word você está utilizando? A implementação varia em cada uma delas..

      Abraço!
      André Lima

      • Maier disse:

        Estou usando a Opção 1: Office Automation

        Maier

        • andrealveslima disse:

          Olá Maier!

          Consegui encontrar um exemplo neste site:

          Adding watermark to Microsoft Office Word document programmatically using Interop

          Adaptando para o exemplo deste artigo, o código C# ficaria assim:

                      Microsoft.Office.Interop.Word.Shape nShape = null;
                      foreach (Microsoft.Office.Interop.Word.Section section in wordDoc.Sections)
                      {
                          nShape = section.Headers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Shapes.AddTextEffect(Microsoft.Office.Core.MsoPresetTextEffect.msoTextEffect1, "TESTE", "Arial", 14, Microsoft.Office.Core.MsoTriState.msoCTrue, Microsoft.Office.Core.MsoTriState.msoFalse, 0, 0);
                          nShape.Fill.Visible = Microsoft.Office.Core.MsoTriState.msoTrue;
                          nShape.Line.Visible = Microsoft.Office.Core.MsoTriState.msoFalse;
                          nShape.Fill.Solid();
                          nShape.Fill.ForeColor.RGB = (Int32)Microsoft.Office.Interop.Word.WdColor.wdColorGray20;
                          nShape.RelativeHorizontalPosition = Microsoft.Office.Interop.Word.WdRelativeHorizontalPosition.wdRelativeHorizontalPositionMargin;
                          nShape.RelativeVerticalPosition = Microsoft.Office.Interop.Word.WdRelativeVerticalPosition.wdRelativeVerticalPositionMargin;
                          // center location
                          nShape.Left = (float)Microsoft.Office.Interop.Word.WdShapePosition.wdShapeCenter;
                          nShape.Top = (float)Microsoft.Office.Interop.Word.WdShapePosition.wdShapeCenter;
                      }
          

          Importante: você precisa adicionar uma referência à dll do Office Core (você encontra essa dll na tela de adição de referências, categoria “Extensions”, nome “Office”)..

          Abraço!
          André Lima

  • José Ângelo disse:

    André, esse post foi ótimo. Ajudou muito.

    Mas a opção salvar é padrão, dentro do

    C:\Users\User\Desktop\. . .\Word\Word\bin\Debug

    Como posso acrescentar um componente, TextBox por exemplo, que aponte outro local para salvar outros arquivos gerados no VS2015?

    Abraço.

    • andrealveslima disse:

      Olá José, muito obrigado pelo comentário!

      Se você não especificar um diretório, por padrão, o arquivo será salvo no diretório da sua aplicação (que é exatamente esse “bin\debug” que você mencionou).. Nesse caso, se voce quiser salvar em algum outro diretório, você teria que especifica-lo na hora de salvar.. Mas, isso depende de qual das alternativas você está utilzando para gerar o arquivo doc.. Qual das opções você está utilizando?

      Abraço!
      André Lima

  • Nícolas C Brisque disse:

    Olá André,
    Gostaria de saber como posso criar uma nova página no Word através da extensão docx

    • andrealveslima disse:

      Olá Nícolas!

      Pelo que vi aqui, os elementos do DocX possuem um método chamado “InsertPageBreakAfterSelf”, que adiciona um page break depois do item atual.. Por exemplo, paragrafo.InsertPageBreakAfterSelf().. Seria isso que você está procurando?

      Abraço!
      André Lima

  • Oslain Ricardo Maciel disse:

    Olá pessoal!

    Tive os mesmos problemas do André Alves Lima para deixar o tamanho de uma imagem correto utilizando o Open XML. Tentei converter EMU em Px mas nada funcionava. Após várias pesquisas encontrei no site abaixo um código em que o desenvolvedor utiliza Inches (polegadas) no DW.Extent do método AddImageToBody, e isso funcionou para o meu caso, onde a imagem que queria adicionar no .docx era um cabeçalho. Fui variando os valores e na tentativa e erro encontrei os valores adequados para o meu caso. Acredito que pode ajudar alguém. Segue o link abaixo:

    http://techqa.info/programming/question/37901004/adding-multiple-images-to-a-word-document-at-a-specific-locations-openxml

    Grande abraço a todos.

    Oslain Ricardo Maciel

    • andrealveslima disse:

      Olá Oslain!

      Muito obrigado por ter vindo aqui para registrar a solução que você encontrou para esse problema.. Com certeza vai ajudar outras pessoas que passarem por essa mesma dificuldade no futuro.. Valeu demais!

      Um forte abraço!
      André Lima

  • Bruno disse:

    Meu código está retornando um erro (‘System.Runtime.InteropServices.COMException’), tentei copiar seu código, para ver se com ele funcionava, mas nem assim, o erro é sempre o mesmo, saberia me informar o que causa isso?

    • andrealveslima disse:

      Olá Bruno!

      Essa é a mensagem de erro completa ou tem mais algum detalhe? A única coisa que me vem na cabeça que possa estar causando isso é a arquitetura que você está utilizando para compilar o projeto.. Você está compilando em x86, x64 ou Any CPU? Tente forçar a compilação em x86 para ver se o erro continua acontecendo..

      Abraço!
      André Lima

  • Jorge disse:

    A biblioteca DocX funcionou perfeitamente, porém a mesma gera um arquivo protegido, o que pode ser feita para habilitar o arquivo para edição?

  • Paulo Almeida disse:

    Ótimo artigo. Deu todas as opções, com vantagens e desvantagens de cada um, exemplos etc. Fiz a escolha pelo DocX e já estou aprendendo mais sobre ele.
    Parabéns pela clareza e profundidade das informações.

    • andrealveslima disse:

      Olá Paulo, muito obrigado pelo comentário! Fico feliz por ter conseguido ajudar.. :)

      Qualquer coisa é só entrar em contato..

      Abraço!
      André Lima

  • Ronaldo BF disse:

    Excelente artigo, é exatamente o que eu estava procurando, parabéns Andre Lima, certamente vai ajudar muitas pessoas. Obrigado.

    • Ronaldo BF disse:

      Se tiver mais artigos sobre o docx eu agradeço.

      • andrealveslima disse:

        Olá novamente, Ronaldo!

        Infelizmente só tenho este artigo mesmo.. Qual está sendo a sua dificuldade exatamente?

        Abraço!
        André Lima

    • andrealveslima disse:

      Valeu, Ronaldo! Muito obrigado pelo comentário! Se ficar com alguma dúvida é só entrar em contato..

      Abraço!
      André Lima

      • Ronaldo BF disse:

        Boa tarde Andre´, voce sabe onde posso encontrar exemplos de DocX em asp.net MVC, sem usar forms?. Agradeço.

        • andrealveslima disse:

          Olá Ronaldo!

          Eu fiz os exemplos desse artigo com Windows Forms só para facilitar (porque eu queria mostrar a utilização de várias bibliotecas).. Mas, você poderia utilizar exatamente o mesmo código em uma Console Application (ou até mesmo no ASP.NET) e eu acredito que funcionaria da mesma maneira, uma vez que o código da geração dos arquivos não depende em nada do Windows Forms..

          Você já tentou implementar o código de uma das opções apresentadas no artigo na sua aplicação MVC? Qual foi a dificuldade?

          Abraço!
          André Lima

          • Ronaldo BF disse:

            Já tentei, mas não consegui instalar o DocX, fiz a referência, contudo sempre dá erro com a dll NOVACODE, diz que ela não existe.

          • andrealveslima disse:

            Olá Ronaldo!

            Você adicionou a referência pelo NuGet e ele não acusou nenhum erro no final do procedimento? Você tentou compilar o projeto depois de ter adicionado a referência (antes de alterar o código)? O projeto compilou corretamente e sem erros / warnings?

            Será que você poderia enviar um screenshot da sua seção de referências e da mensagem de erro exata?

            Última pergunta: o seu projeto é com o .NET Framework “full” ou com o .NET Core?

            Abraço!
            André Lima

  • Emerson disse:

    Olá, estou usando o Office Automation, e gostaria de saber como faço para salvar o arquivo em outro destino, sem ser na pasta padrão, também gostaria de saber como faço para exibir uma mensagem dizendo se o arquivo foi criado mesmo ou se houve algum problema.

    • andrealveslima disse:

      Olá Emerson!

      Para salvar o arquivo em outro diretório, você pode utilizar o método “SaveAs2” (documentação aqui).. Quanto a saber se o mesmo foi criado ou se deu problema, eu não tenho certeza, mas acredito que você pode colocar a chamada dentro de um bloco try/catch.. Se o código cair na parte do “catch” significa que ocorreu algum erro durante a escrita do arquivo..

      Abraço!
      André Lima

      • Emerson disse:

        Muito obrigado pelas respostas, você tem mais conteudo sobre gerar arquivos Word com C#?

        • andrealveslima disse:

          Olá Emerson!

          Infelizmente só tenho esse artigo sobre esse tema por enquanto.. Além das dúvidas que você postou no seu comentário anterior, você está com alguma dificuldade específica ainda?

          Abraço!
          André Lima

          • Emerson disse:

            Atualmente estou com problemas para configurar as margens, na configuração das páginas, se souber como posso fazer, agradeço

          • andrealveslima disse:

            Olá Emerson!

            Você já tentou utilizar as propriedades TopMargin, BottomMargin, LeftMargin e RightMargin do PageSetup do documento? Algo como:

            wordDoc.PageSetup.TopMargin = 10;
            

            Abraço!
            André Lima

  • Emerson disse:

    Olá

    Gostaria de saber como configurar para que o texto sair maiusculo

  • Moisés Santos disse:

    André, boa noite.
    Gostaria de parabenizá-lo pelos ensinamentos valiosos que venho encontrando em seu site.
    Programo a um bom tempo em Delphi e agora estou me aventurando em “águas nunca antes navegadas” no C# e seu site vem me ajudando bastante.
    Continue assim meu amigo, um grande abraço.

    • andrealveslima disse:

      Olá Moisés, muito obrigado pelo comentário!

      Fico feliz que você esteja gostando.. Qualquer dificuldade é só entrar em contato..

      Abraço!
      André Lima

  • Emerson disse:

    Obrigado, desculpa incomodar novamente, mas sabe como posso guardar as linhas de código responsáveis por criar uma linha no word em um método, para economizar código??

    • andrealveslima disse:

      Olá Emerson!

      Você só precisa separar o código dentro de um método, passando o texto a ser adicionado e a variável “WordDocument” a ser utilizada.. Não consegui entender direito onde é que está a dificuldade.. Você pode detalhar melhor?

      Abraço!
      André Lima

  • Emerson disse:

    Poderia me ajudar em gerar um sumário, não faço ideia por onde começar!

  • Emerson disse:

    Boa noite, gostaria de saber se você possui informações quanto a gerar as referencias bibliograficas, se tiver, por favor me mande. grato.

    • andrealveslima disse:

      Olá Emerson!

      Que referências bibliográficas? Esse tipo de informação você tem que adicionar manualmente no documento, como qualquer outra parte do documento.. É texto normal formatado..

      Agora, se você estiver falando de sumário, eu já respondi o seu comentário uns tempos atrás passando alguns links de como fazer isso.. Procura aqui na página por “sumário” e você vai encontrar o seu comentário e a minha resposta..

      Abraço!
      André Lima

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *