C the microsoft office interop word

The C# programming language includes capabilities that make working with Microsoft Office API objects easier. With the advent of named and optional arguments, introduction of the dynamic type in .NET, and the ability to pass arguments to the reference parameters in COM methods, C# 4.0 quickly became the language of choice for working with COM and Interop objects.

This article talks about office interop objects in C# and how you can use them to interact with Microsoft Word and Microsoft Excel. Code examples are also provided to illustrate the concepts covered.

Prerequisites for working with Interop Objects

Visual Studio 2019 or Visual Studio 2022 must be installed on your computer to work with the code samples demonstrated in this C# tutorial. In this example, we will be using Visual Studio 2022. If you don’t have it installed in your computer, you can download it from here.

As of this writing, Visual Studio 2022 RC 2 has been released. You should also have Microsoft Office Excel 2007 or Microsoft Office Word 2007 (or their later versions) installed on your computer.

Read: Code Refactoring Tips for C#.

How to Create a New Console Application in Visual Studio

In this section we will examine how we can create a new console application project in Visual Studio 2022. Assuming Visual Studio 2022 is installed on your system, adhere to the steps given below to create a new Console Application project:

  • Start the Visual Studio 2022 IDE.
  • Click on “Create new project.
  • In the “Create new project” page, select C# in the language drop down list, Windows from the Platforms list and Console from the “Project types” list.
  • Select Console App (.NET Framework) from the project templates displayed.

Create New Project in Visual Studio

  • Click Next.
  • In the “Configure your new project” screen, specify the project’s name and the location where you would want the project to be created.
  • Before you move on to the next screen, you can optionally select the “Place solution and project in the same directory” checkbox.

Configure Visual Studio Projects

  • Click Next.
  • In the Additional Information screen, specify the Framework version you would like to use. We will use .NET Framework 4.8 in this example.

Configure VS Projects

  • Click Create to complete the process.

This will create a new .NET Framework Console application project in Visual Studio 2022. We will use this project in the sections that follow.

Install NuGet Packages

Install the following libraries from NuGet using the NuGet Package Manager or from the NuGet Package Manager Console:

Microsoft.Office.Interop.Word
Microsoft.Office.Interop.Excel

Read: Working with C# Math Operators.

How to Program Office Interop Objects in C#

In this section we will examine how to work with Office Interop objects and use them to connect to Microsoft Word and Excel and read/write data.

You must add the following using directives in your program for working with Word and Excel respectively when using Office interop objects:

using Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.Word;

Working with Excel Interop Objects in C#

To begin, create a new Excel document named Test.xslx as a sample Excel file present in the root directory of the D:> drive. We will use this file in the following example.

You should create an instance of the Application class pertaining to the Microsoft.Office.Interop.Excel library for communicating with Excel. To do this, write the following C# code:

Application excelApplication = new Application();

The next step is to create an instance of the Workbook class to access a Workbook in Excel. You can create an instance of Workbook using the following code:

Workbook excelWorkBook = excel.Workbooks.Open(@"D:Test.xslx");

To read the name of the workbook, you can use the Name property of the workbook instance as shown in the code snippet given below:

string workbookName = excelWorkBook.Name;

The following code listing illustrates how you can display the value of the first cell of the first worksheet of the Excel document:

int worksheetcount = excelWorkBook.Worksheets.Count;
if (worksheetcount > 0) {
  Worksheet worksheet = (Worksheet) excelWorkBook.Worksheets[1];
  string worksheetName = worksheet.Name;
  var data = ((Range) worksheet.Cells[row, column]).Value;
  Console.WriteLine(data);
} else {
  Console.WriteLine("No worksheets available");
}

Here’s the complete code listing for your reference:

using Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.Word;
using System;
using System.Runtime.InteropServices;

namespace OfficeInteropDemoApp
{
    class Program
    {
        static void Main(string[] args)
        {
            string filename = @"D:Test.xlsx";
            DisplayExcelCellValue(filename, 1, 1);
            Console.Read();
        }

        static void DisplayExcelCellValue(string filename, 
        int row, int column)
        {
            Microsoft.Office.Interop.Excel.Application 
            excelApplication = null;
            try
            {
                excelApplication = new 
                Microsoft.Office.Interop.Excel.Application();
                Workbook excelWorkBook = 
                excelApplication.Workbooks.Open(filename);
                string workbookName = excelWorkBook.Name;
                int worksheetcount = excelWorkBook.Worksheets.Count;

                if (worksheetcount > 0)
                {
                    Worksheet worksheet = 
                   (Worksheet)excelWorkBook.Worksheets[1];
                    string firstworksheetname = worksheet.Name;
                    var data = ((Microsoft.Office.Interop.Excel.Range)
                    worksheet.Cells[row, column]).Value;
                    Console.WriteLine(data);
                }
                else
                {
                    Console.WriteLine("No worksheets available");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                if (excelApplication != null)
                {
                    excelApplication.Quit();
                    Marshal.FinalReleaseComObject(excelApplication);
                }
            }
        }
    }
}

Refer to the code listing given above. Note, the finally block of the DisplayExcelCellValue method. The Quit method is called on the Excel application instance to stop the application. Finally, a call to Marshall.FinalReleaseComObject sets the reference counter of the Excel application instance to 0.

The following code listing illustrates how you can create a new Excel document using Office Interop in C#. Note how a new workbook has been created:

static void CreateExcelDocument() 
{
	Microsoft.Office.Interop.Excel.Application excelApplication = null;

	try {
		excelApplication = new 
            Microsoft.Office.Interop.Excel.Application();
		Workbook excelWorkBook = excelApplication.Workbooks.Add();
		Worksheet worksheet = (Worksheet) excelWorkBook.Worksheets[1];
		worksheet.Cells[1, 1] = "Product Id";
		worksheet.Cells[1, 2] = "Product Name";
		worksheet.Cells[2, 1] = "1";
		worksheet.Cells[2, 2] = "Lenovo Laptop";
		worksheet.Cells[3, 1] = "2";
		worksheet.Cells[3, 2] = "DELL Laptop";
		excelWorkBook.SaveAs(@"D:Test.xls");
	}
	catch(Exception ex) {
		Console.WriteLine(ex.Message);
	}
	finally {
		if (excelApplication != null) {
			excelApplication.Quit();
			Marshal.FinalReleaseComObject(excelApplication);
		}
	}
}

When you run this code, a new Excel document will be created at the path specified with the following content inside:

C# Interop Objects Tutorial

Read: Working with Strings in C#.

Working with Word Interop Objects in C#

To work with Microsoft Word, you would need to create an instance of Microsoft.Office.Interop.Word.Application. Like Excel, this instance would be used to communicate with a Word document.

Microsoft.Office.Interop.Word.Application wordApplication = new Microsoft.Office.Interop.Word.Application();

The next step is to create a document instance using the Documents property of the Microsoft.Office.Interop.Word.Application instance we just created, as shown in the C# code snippet given below:

wordApplication.Documents.Add();

Next, you can create a paragraph and add some text to it using the as shown in the code snippet shown below:

var paragraph = document.Paragraphs.Add();
paragraph.Range.Text = "This is a sample text to demonstrate how Interop works...";

Then you can save the Word document using this code:

wordApplication.ActiveDocument.SaveAs(@"D:Test.doc", WdSaveFormat.wdFormatDocument);

Here is the complete code listing showing how to work with Microsoft Word Interop Objects in C# for your reference:

using Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.Word;
using System;
using System.Runtime.InteropServices;

namespace OfficeInteropDemoApp
{
    class Program
    {
        static void Main(string[] args)
        {
            string filename = @"D:Test.doc";
            CreateWordDocument(filename);
            Console.Read();
        }

        static void CreateWordDocument(string filename)
        {
            Microsoft.Office.Interop.Word.Application 
            wordApplication = null;
            try
            {
                wordApplication = new 
                Microsoft.Office.Interop.Word.Application();
                var document = wordApplication.Documents.Add();
                var paragraph = document.Paragraphs.Add();
                paragraph.Range.Text = "This is a sample text to 
                demonstrate how Interop works...";
                wordApplication.ActiveDocument.SaveAs(filename, 
                WdSaveFormat.wdFormatDocument);
                document.Close();

            }
            finally
            {
                if (wordApplication != null)
                {
                    wordApplication.Quit();
                    Marshal.FinalReleaseComObject(wordApplication);
                }
            }
        }
    }
}

To read a Word document and display each word of the document you can use the following C# code:

static void ReadWordDocument(string filename)
        {
            Microsoft.Office.Interop.Word.Application 
            wordApplication = null;
            try
            {
                wordApplication = new 
                Microsoft.Office.Interop.Word.Application();
                Document document = 
                wordApplication.Documents.Open(filename);

                int count = document.Words.Count;
                for (int i = 1; i <= count; i++)
                {
                    string text = document.Words[i].Text;
                    Console.WriteLine(text);
                }
            }
            catch(Exception ex)
            {
                Console.Write(ex.Message);
            }
            finally
            {
                if (wordApplication != null)
                {
                    wordApplication.Quit();
                    Marshal.FinalReleaseComObject(wordApplication);
                }
            }
        }

Note how the Words property of the Word application instance has been used to retrieve the words contained in the document.

C# Interop Objects Tutorial

In this article we have examined how we can access Microsoft Office Interop objects using C#. Since there is still no support for working with Interop objects in .NET Core, we have created a .NET Framework Console Application in this example.

<< Back to C-SHARP

Use Microsoft.Office.Interop.Word. Open a DOC file and read the text in it.

Microsoft Word can be used with C# code. You have a Microsoft Word document (.doc) and want to read it in your C# program. With the Microsoft.Office.Interop.Word assembly, we get the contents and formatting from the document.

Tip: Add the Microsoft.Office.Interop.Word assembly to your project. Go to Project -> Add Reference.

Example. First, we show the file we will read. It contains three paragraphs containing one word each. The program first instantiates an Application instance and then we call Documents.Open on that variable.

Next: We loop through the Words collection and read the Text property on each element. We then display and call Quit.

Word document: word.doc

One

Two

three

C# program that uses Microsoft Word interop

using System;
using Microsoft.Office.Interop.Word;

class Program
{
static void Main()
{
// Open a doc file.
Application application = new Application();
Document document = application.Documents.Open(«C:\word.doc»);

// Loop through all words in the document.
int count = document.Words.Count;
for (int i = 1; i <= count; i++)
{
// Write the word.
string text = document.Words[i].Text;
Console.WriteLine(«Word {0} = {1}», i, text);
}
// Close word.
application.Quit();
}
}

Output

Word 1 = One
Word 2 =
Word 3 = Two
Word 4 =
Word 5 = three
Word 6 =

Empty paragraphs. We see that Word 2, Word 4, and Word 6 are empty. The empty paragraphs in the input file are considered words. If you have multiple words in a paragraph, they will each be separate in the Words collection.

So: In Interop.Word, a paragraph is made up of a collection of one or more words.

Quit. Why is the application.Quit statement important? If you don’t include this, the WINWORD.EXE application will remain in the process list. Then, when this program is run again, a new one will be started. This wastes memory.

Note: It is important to iterate on Words from 1 to Count inclusive. Correction suggested by Robert Ford.

Summary. We looked at the Microsoft.Office Interop.Word assembly and learned how to read in data from a Word document. This can be useful when you have DOC or DOCX files and want to programmatically read in data from your C# program.

Related Links:

  • C# Array Examples, String Arrays
  • C# ArrayList Examples
  • C# ArraySegment: Get Array Range, Offset and Count
  • C# break Statement
  • C# Buffer BlockCopy Example
  • C# BufferedStream: Optimize Read and Write
  • 404 Not Found
  • C# 24 Hour Time Formats
  • C# 2D Array Examples
  • 7 Zip Command Line Examples
  • C# 7 Zip Executable (Process.Start)
  • C# All Method: All Elements Match a Condition
  • C# Alphabetize String
  • C# Alphanumeric Sorting
  • C# Arithmetic Expression Optimization
  • C# Array.AsReadOnly Method (ObjectModel)
  • C# Array.BinarySearch Method
  • C# Array.Clear Examples
  • C# Array.IndexOf, LastIndexOf: Search Arrays
  • C# async, await Examples
  • C# Attribute Examples
  • C# Average Method
  • C# BackgroundWorker
  • C# base Keyword
  • C# String Between, Before, After
  • C# Binary Representation int (Convert, toBase 2)
  • C# BinarySearch List
  • C# bool Array (Memory Usage, One Byte Per Element)
  • C# bool.Parse, TryParse: Convert String to Bool
  • C# bool Type
  • C# Array Length Property, Get Size of Array
  • C# Button Example
  • C# Byte Array: Memory Usage, Read All Bytes
  • C# Byte and sbyte Types
  • C# Capacity for List, Dictionary
  • C# Case Insensitive Dictionary
  • C# case Example (Switch Case)
  • C# Char Array
  • C# Checked and Unchecked Keywords
  • C# CheckedListBox: Windows Forms
  • C# Color Table
  • C# Color Examples: FromKnownColor, FromName
  • C# ColorDialog Example
  • C# Comment: Single Line and Multiline
  • C# Concat Extension: Combine Lists and Arrays
  • C# Conditional Attribute
  • C# Console Color, Text and BackgroundColor
  • C# String Clone() method
  • C# Constructor Examples
  • C# Contains Extension Method
  • C# String GetTypeCode() method
  • C# String ToLowerInvariant() method
  • C# Customized Dialog Box
  • C# DataColumn Example: Columns.Add
  • C# DataGridView Add Rows
  • DataGridView Columns, Edit Columns Dialog
  • C# DataGridView Row Colors (Alternating)
  • C# DataGridView Tutorial
  • C# DataGridView
  • C# DataRow Examples
  • C# DataSet Examples
  • C# DataSource Example
  • C# DataTable Compare Rows
  • C# DataTable foreach Loop
  • C# DataTable RowChanged Example: AcceptChanges
  • C# DataTable Select Example
  • C# DataTable Examples
  • C# DataView Examples
  • C# String ToString() method
  • C# String ToUpper() method
  • C# Digit Separator
  • C# DateTime.MinValue (Null DateTime)
  • C# DateTime.Month Property
  • C# DateTime.Parse: Convert String to DateTime
  • C# DateTime Subtract Method
  • C# Decompress GZIP
  • C# Remove Duplicates From List
  • C# dynamic Keyword
  • C# ElementAt, ElementAtOrDefault Use
  • C# Encapsulate Field
  • C# Enum Array Example, Use Enum as Array Index
  • C# enum Flags Attribute Examples
  • C# Enum ToString: Convert Enum to String
  • C# enum Examples
  • C# Enumerable.Range, Repeat and Empty
  • C# Environment Type
  • C# EventLog Example
  • C# Exception Handling
  • C# explicit and implicit Keywords
  • C# Factory Design Pattern
  • C# File.Copy Examples
  • C# typeof and nameof Operators
  • C# String TrimEnd() method
  • C# var Examples
  • C# virtual Keyword
  • C# void Method, Return No Value
  • C# volatile Example
  • C# WebBrowser Control (Navigate Method)
  • C# WebClient: DownloadData, Headers
  • C# Where Method and Keyword
  • C# String TrimStart() method
  • C# delegate Keyword
  • C# descending, ascending Keywords
  • C# while Loop Examples
  • C# Whitespace Methods: Convert UNIX, Windows Newlines
  • C# XmlReader, Parse XML File
  • C# XmlTextReader
  • C# XmlTextWriter
  • C# XmlWriter, Create XML File
  • C# XOR Operator (Bitwise)
  • C# yield Example
  • C# float Numbers
  • FlowLayoutPanel Control
  • C# Focused Property
  • C# FolderBrowserDialog Control
  • C# Font Type: FontFamily and FontStyle
  • C# FontDialog Example
  • C# for Loop Examples
  • C# foreach Loop Examples
  • ForeColor, BackColor: Windows Forms
  • C# Form: Event Handlers
  • C# Contains String Method
  • C# ContainsValue Method (Value Exists in Dictionary)
  • C# ContextMenuStrip Example
  • C# continue Keyword
  • C# Control: Windows Forms
  • C# Windows Forms Controls
  • C# Convert Char Array to String
  • C# Convert Char to String
  • C# Convert Days to Months
  • C# Convert String to Byte Array
  • C# String Format
  • C# Func Object (Lambda That Returns a Value)
  • C# GC.Collect Examples: CollectionCount, GetTotalMemory
  • C# Path.GetDirectoryName (Remove File From Path)
  • C# goto Examples
  • C# HttpClient Example: System.Net.Http
  • ASP.NET HttpContext Request Property
  • IL Disassembler Tutorial
  • C# Intermediate Language (IL)
  • C# IndexOf Examples
  • C# IndexOfAny Examples
  • C# Initialize Array
  • C# Initialize List
  • C# InitializeComponent Method: Windows Forms
  • C# Inline Optimization
  • C# Dictionary Equals: If Contents Are the Same
  • C# Dictionary Versus List Loop
  • C# Dictionary Order, Use Keys Added Last
  • C# Dictionary Size and Performance
  • C# Dictionary Versus List Lookup Time
  • C# Dictionary Examples
  • C# Get Directory Size (Total Bytes in All Files)
  • C# Directory Type
  • C# Distinct Method, Get Unique Elements Only
  • C# Divide by Powers of Two (Bitwise Shift)
  • C# Divide Numbers (Cast Ints)
  • C# DomainUpDown Control Example
  • C# Double Type: double.MaxValue, double.Parse
  • C# Remove Duplicate Chars
  • C# IEqualityComparer
  • C# If Preprocessing Directive: Elif and Endif
  • C# If Versus Switch Performance
  • C# if Statement
  • C# int.MaxValue, MinValue (Get Lowest Number)
  • C# Program to reverse number
  • C# Int and uint Types
  • C# Integer Append Optimization
  • C# Keywords
  • C# Label Example: Windows Forms
  • C# Lambda Expressions
  • C# LastIndexOf Examples
  • C# Last, LastOrDefault Methods
  • C# Mutex Example (OpenExisting)
  • C# Named Parameters
  • C# Let Keyword (Use Variable in Query Expression)
  • C# Levenshtein Distance
  • C# LinkLabel Example: Windows Forms
  • C# LINQ
  • C# List Add Method, Append Element to List
  • C# List AddRange, InsertRange (Append Array to List)
  • C# List Clear Example
  • C# List Contains Method
  • C# List Remove Examples
  • C# List Examples
  • C# ListBox Tutorial (DataSource, SelectedIndex)
  • C# ListView Tutorial: Windows Forms
  • C# Maze Pathfinding Algorithm
  • C# Memoization
  • C# Memory Usage for Arrays of Objects
  • C# MessageBox.Show Examples
  • C# Method Call Depth Performance
  • C# Method Parameter Performance
  • C# Method Size Optimization
  • C# Multidimensional Array
  • C# MultiMap Class (Dictionary of Lists)
  • C# Optimization
  • C# new Keyword
  • C# NotifyIcon: Windows Forms
  • C# NotImplementedException
  • C# Null Array
  • C# String GetType() method
  • C# Null Coalescing and Null Conditional Operators
  • C# Null List (NullReferenceException)
  • C# Numeric Casts
  • C# NumericUpDown Control: Windows Forms
  • C# object.ReferenceEquals Method
  • C# Object Examples
  • C# Optional Parameters
  • C# Prime Number
  • C# OrderBy, OrderByDescending Examples
  • C# Process Examples (Process.Start)
  • Panel, Windows Forms (Create Group of Controls)
  • C# Path Examples
  • C# Get Percentage From Number With Format String
  • ASP.NET PhysicalApplicationPath
  • C# PictureBox: Windows Forms
  • C# PNG Optimization
  • C# Position Windows: Use WorkingArea and Location
  • Visual Studio Post Build, Pre Build Macros
  • C# ProfileOptimization
  • C# ProgressBar Example
  • C# Property Examples
  • C# PropertyGrid: Windows Forms
  • C# Protected and internal Keywords
  • C# Public and private Methods
  • C# Remove Punctuation From String
  • C# Query Windows Forms (Controls.OfType)
  • C# Queryable: IQueryable Interface and AsQueryable
  • ASP.NET QueryString Examples
  • C# Queue Collection: Enqueue
  • C# RadioButton Use: Windows Forms
  • C# ReadOnlyCollection Use (ObjectModel)
  • C# Recursion Optimization
  • C# Recursive File List: GetFiles With AllDirectories
  • C# ref Keyword
  • C# Reflection Examples
  • C# Regex.Escape and Unescape Methods
  • C# StringBuilder Examples
  • C# StringComparison and StringComparer
  • C# StringReader Class (Get Parts of Strings)
  • C# String GetEnumerator() method
  • C# String GetHashCode() method
  • C# Regex Versus Loop: Use For Loop Instead of Regex
  • C# Regex.Match Examples: Regular Expressions
  • C# RemoveAll: Use Lambda to Delete From List
  • C# Replace String Examples
  • ASP.NET Response.BinaryWrite
  • ASP.NET Response.Write
  • C# Return Optimization: out Performance
  • C# SaveFileDialog: Use ShowDialog and FileName
  • C# Scraping HTML Links
  • C# sealed Keyword
  • C# Seek File Examples: ReadBytes
  • C# select new Example: LINQ
  • C# Select Method (Use Lambda to Modify Elements)
  • C# Serialize List (Write to File With BinaryFormatter)
  • C# Settings.settings in Visual Studio
  • C# Shuffle Array: KeyValuePair and List
  • C# Single and Double Types
  • C# Single Instance Windows Form
  • C# Snippet Examples
  • C# Sort DateTime List
  • C# Sort List With Lambda, Comparison Method
  • C# Sort Number Strings
  • C# Sort Examples: Arrays and Lists
  • C# SortedDictionary
  • C# SortedList
  • C# SortedSet Examples
  • C# Split String Examples
  • C# String Copy() method
  • C# SplitContainer: Windows Forms
  • C# SqlClient Tutorial: SqlConnection, SqlCommand
  • C# SqlCommand Example: SELECT TOP, ORDER BY
  • C# SqlCommandBuilder Example: GetInsertCommand
  • C# SqlConnection Example: Using, SqlCommand
  • C# SqlDataAdapter Example
  • C# SqlDataReader: GetInt32, GetString
  • C# SqlParameter Example: Constructor, Add
  • C# Stack Collection: Push, Pop
  • C# Static List: Global List Variable
  • C# Static Regex
  • C# Static String
  • C# static Keyword
  • C# StatusStrip Example: Windows Forms
  • C# String Chars (Get Char at Index)
  • C# string.Concat Examples
  • C# String Interpolation Examples
  • C# string.Join Examples
  • C# String Performance, Memory Usage Info
  • C# String Property
  • C# String Slice, Get Substring Between Indexes
  • C# String Switch Examples
  • C# String
  • C# StringBuilder Append Performance
  • C# StringBuilder Cache
  • C# ToBase64String (Data URI Image)
  • C# Struct Versus Class
  • C# struct Examples
  • C# Substring Examples
  • C# Numeric Suffix Examples
  • C# switch Examples
  • C# String IsNormalized() method
  • C# TabControl: Windows Forms
  • TableLayoutPanel: Windows Forms
  • C# Take and TakeWhile Examples
  • C# Task Examples (Task.Run, ContinueWith and Wait)
  • C# Ternary Operator
  • C# Text Property: Windows Forms
  • C# TextBox.AppendText Method
  • C# TextBox Example
  • C# TextChanged Event
  • C# TextFieldParser Examples: Read CSV
  • C# ThreadStart and ParameterizedThreadStart
  • C# throw Keyword Examples
  • C# Timer Examples
  • C# TimeSpan Examples
  • C# TrimEnd and TrimStart
  • C# True and False
  • C# Truncate String
  • C# String ToLower() method
  • C# String ToCharArray() method
  • C# String ToUpperInvariant() method
  • C# String Trim() method
  • C# Assign Variables Optimization
  • C# Array.Resize Examples
  • C# Array.Sort: Keys, Values and Ranges
  • C# Array.Reverse Example
  • C# Array Slice, Get Elements Between Indexes
  • C# Array.TrueForAll: Use Lambda to Test All Elements
  • C# ArrayTypeMismatchException
  • C# as: Cast Examples
  • C# ASCII Table
  • C# ASCII Transformation, Convert Char to Index
  • C# AsEnumerable Method
  • C# AsParallel Example
  • ASP.NET AspLiteral
  • C# BaseStream Property
  • C# Console.Beep Example
  • C# Benchmark
  • C# BinaryReader Example (Use ReadInt32)
  • C# BinaryWriter Type
  • C# BitArray Examples
  • C# BitConverter Examples
  • C# Bitcount Examples
  • C# Bool Methods, Return True and False
  • C# bool Sort Examples (True to False)
  • C# Caesar Cipher
  • C# Cast Extension: System.Linq
  • C# Cast to Int (Convert Double to Int)
  • C# Cast Examples
  • C# catch Examples
  • C# Change Characters in String (ToCharArray, For Loop)
  • C# Char Combine: Get String From Chars
  • C# char.IsDigit (If Char Is Between 0 and 9)
  • C# char.IsLower and IsUpper
  • C# Character Literal (const char)
  • C# Char Lowercase Optimization
  • C# Char Test (If Char in String Equals a Value)
  • C# char.ToLower and ToUpper
  • C# char Examples
  • C# abstract Keyword
  • C# Action Object (Lambda That Returns Void)
  • C# Aggregate: Use Lambda to Accumulate Value
  • C# AggressiveInlining: MethodImpl Attribute
  • C# Anagram Method
  • C# And Bitwise Operator
  • C# Anonymous Function (Delegate With No Name)
  • C# Any Method, Returns True If Match Exists
  • C# StringBuilder Append and AppendLine
  • C# StringBuilder AppendFormat
  • ASP.NET appSettings Example
  • C# ArgumentException: Invalid Arguments
  • C# Array.ConvertAll, Change Type of Elements
  • C# Array.Copy Examples
  • C# Array.CreateInstance Method
  • C# Array and Dictionary Test, Integer Lookups
  • C# Array.Exists Method, Search Arrays
  • C# Array.Find Examples, Search Array With Lambda
  • C# Array.ForEach: Use Lambda on Every Element
  • C# Array Versus List Memory Usage
  • C# Array Property, Return Empty Array
  • C# CharEnumerator
  • C# Chart, Windows Forms (Series and Points)
  • C# CheckBox: Windows Forms
  • C# class Examples
  • C# Clear Dictionary: Remove All Keys
  • C# Clone Examples: ICloneable Interface
  • C# Closest Date (Find Dates Nearest in Time)
  • C# Combine Arrays: List, Array.Copy and Buffer.BlockCopy
  • C# Combine Dictionary Keys
  • C# ComboBox: Windows Forms
  • C# CompareTo Int Method
  • C# Comparison Object, Used With Array.Sort
  • C# Compress Data: GZIP
  • C# Console.Read Method
  • C# Console.ReadKey Example
  • C# Console.ReadLine Example (While Loop)
  • C# Console.SetOut and Console.SetIn
  • C# Console.WindowHeight
  • C# Console.Write, Append With No Newline
  • C# Console.WriteLine (Print)
  • C# const Example
  • C# Constraint Puzzle Solver
  • C# Count Characters in String
  • C# Count, Dictionary (Get Number of Keys)
  • C# Count Letter Frequencies
  • C# Count Extension Method: Use Lambda to Count
  • C# CSV Methods (Parse and Segment)
  • C# DataRow Field Method: Cast DataTable Cell
  • C# Get Day Count Elapsed From DateTime
  • C# DateTime Format
  • C# DateTime.Now (Current Time)
  • C# DateTime.Today (Current Day With Zero Time)
  • C# DateTime.TryParse and TryParseExact
  • C# DateTime Examples
  • C# DateTimePicker Example
  • C# Debug.Write Examples
  • C# Visual Studio Debugging Tutorial
  • C# decimal Examples
  • C# DayOfWeek
  • C# Enum.Format Method (typeof Enum)
  • C# Enum.GetName, GetNames: Get String Array From Enum
  • C# Enum.Parse, TryParse: Convert String to Enum
  • C# Error and Warning Directives
  • C# ErrorProvider Control: Windows Forms
  • C# event Examples
  • C# Get Every Nth Element From List (Modulo)
  • C# Excel Interop Example
  • C# Except (Remove Elements From Collection)
  • C# Extension Method
  • C# extern alias Example
  • C# Convert Feet, Inches
  • C# File.Delete
  • C# File Equals: Compare Files
  • C# File.Exists Method
  • C# try Keyword
  • C# TryGetValue (Get Value From Dictionary)
  • C# Tuple Examples
  • C# Type Class: Returned by typeof, GetType
  • C# TypeInitializationException
  • C# Union: Combine and Remove Duplicate Elements
  • C# Unreachable Code Detected
  • C# Unsafe Keyword: Fixed, Pointers
  • C# Uppercase First Letter
  • C# Uri and UriBuilder Classes
  • C# Using Alias Example
  • C# using Statement: Dispose and IDisposable
  • C# value Keyword
  • C# ValueTuple Examples (System.ValueTuple, ToTuple)
  • C# ValueType Examples
  • C# Variable Initializer for Class Field
  • C# Word Count
  • C# Word Interop: Microsoft.Office.Interop.Word
  • C# XElement Example (XElement.Load, XName)
  • C# Zip Method (Use Lambda on Two Collections)
  • C# File.Move Method, Rename File
  • C# File.Open Examples
  • C# File.ReadAllBytes, Get Byte Array From File
  • C# File.ReadAllLines, Get String Array From File
  • C# File.ReadAllText, Get String From File
  • C# File.ReadLines, Use foreach Over Strings
  • C# File.Replace Method
  • C# FileInfo Length, Get File Size
  • C# FileInfo Examples
  • C# File Handling
  • C# Filename With Date Example (DateTime.Now)
  • C# FileNotFoundException (catch Example)
  • C# FileStream Length, Get Byte Count From File
  • C# FileStream Example, File.Create
  • C# FileSystemWatcher Tutorial (Changed, e.Name)
  • C# finally Keyword
  • C# First Sentence
  • C# FirstOrDefault (Get First Element If It Exists)
  • C# Fisher Yates Shuffle: Generic Method
  • C# fixed Keyword (unsafe)
  • C# Flatten Array (Convert 2D to 1D)
  • C# First Words in String
  • C# First (Get Matching Element With Lambda)
  • C# ContainsKey Method (Key Exists in Dictionary)
  • C# Convert ArrayList to Array (Use ToArray)
  • C# Convert ArrayList to List
  • C# Convert Bool to Int
  • C# Convert Bytes to Megabytes
  • C# Convert Degrees Celsius to Fahrenheit
  • C# Convert Dictionary to List
  • C# Convert Dictionary to String (Write to File)
  • C# Convert List to Array
  • C# Convert List to DataTable (DataGridView)
  • C# Convert List to String
  • C# Convert Miles to Kilometers
  • C# Convert Milliseconds, Seconds, Minutes
  • C# Convert Nanoseconds, Microseconds, Milliseconds
  • C# Convert String Array to String
  • C# Convert TimeSpan to Long
  • C# Convert Types
  • C# Copy Dictionary
  • C# Count Elements in Array and List
  • C# FromOADate and Excel Dates
  • C# Generic Class, Generic Method Examples
  • C# GetEnumerator: While MoveNext, Get Current
  • C# GetHashCode (String Method)
  • C# Thumbnail Image With GetThumbnailImage
  • C# GetType Method
  • C# Global Variable Examples (Public Static Property)
  • ASP.NET Global Variables Example
  • C# Group By Operator: LINQ
  • GroupBox: Windows Forms
  • C# GroupBy Method: LINQ
  • C# GroupJoin Method
  • C# GZipStream Example (DeflateStream)
  • C# HashSet Examples
  • C# Hashtable Examples
  • HelpProvider Control Use
  • C# HTML and XML Handling
  • C# HtmlEncode and HtmlDecode
  • C# HtmlTextWriter Example
  • C# HttpUtility.HtmlEncode Methods
  • C# HybridDictionary
  • C# default Operator
  • C# DefaultIfEmpty Method
  • C# Define and Undef Directives
  • C# Destructor
  • C# DialogResult: Windows Forms
  • C# Dictionary, Read and Write Binary File
  • C# Dictionary Memory
  • C# Dictionary Optimization, Increase Capacity
  • C# Dictionary Optimization, Test With ContainsKey
  • C# DictionaryEntry Example (Hashtable)
  • C# Directives
  • C# Directory.CreateDirectory, Create New Folder
  • C# Directory.GetFiles Example (Get List of Files)
  • C# DivideByZeroException
  • C# DllImport Attribute
  • C# Do While Loop Example
  • C# DriveInfo Examples
  • C# DropDownItems Control
  • C# IComparable Example, CompareTo: Sort Objects
  • C# IDictionary Generic Interface
  • C# IEnumerable Examples
  • C# IList Generic Interface: List and Array
  • C# Image Type
  • C# ImageList Use: Windows Forms
  • C# Increment String That Contains a Number
  • C# Increment, Preincrement and Decrement Ints
  • Dot Net Perls
  • C# Indexer Examples (This Keyword, get and set)
  • C# IndexOutOfRangeException
  • C# Inheritance
  • C# Insert String Examples
  • C# int Array
  • C# Interface Examples
  • C# Interlocked Examples: Add, CompareExchange
  • C# Intersect: Get Common Elements
  • C# InvalidCastException
  • C# InvalidOperationException: Collection Was Modified
  • C# IOException Type: File, Directory Not Found
  • C# IOrderedEnumerable (Query Expression With orderby)
  • C# is: Cast Examples
  • C# IsFixedSize, IsReadOnly and IsSynchronized Arrays
  • C# string.IsNullOrEmpty, IsNullOrWhiteSpace
  • C# IsSorted Method: If Array Is Already Sorted
  • C# Jagged Array Examples
  • C# join Examples (LINQ)
  • C# KeyCode Property and KeyDown
  • C# KeyNotFoundException: Key Not Present in Dictionary
  • C# KeyValuePair Examples
  • C# Line Count for File
  • C# Line Directive
  • C# LinkedList
  • C# List CopyTo (Copy List Elements to Array)
  • C# List Equals (If Elements Are the Same)
  • C# List Find and Exists Examples
  • C# List Insert Performance
  • ASP.NET LiteralControl Example
  • C# Locality Optimizations (Memory Hierarchy)
  • C# lock Keyword
  • C# Long and ulong Types
  • C# Loop Over String Chars: Foreach, For
  • C# Loop Over String Array
  • C# Main args Examples
  • C# Map Example
  • ASP.NET MapPath: Virtual and Physical Paths
  • C# Mask Optimization
  • C# MaskedTextBox Example
  • C# Math.Abs: Absolute Value
  • C# Math.Ceiling Usage
  • C# Math.Floor Method
  • C# Math.Max and Math.Min Examples
  • C# Math.Pow Method, Exponents
  • C# Math.Round Examples: MidpointRounding
  • C# Math Type
  • C# Max and Min: Get Highest or Lowest Element
  • C# MemoryFailPoint and InsufficientMemoryException
  • C# MemoryStream: Use Stream on Byte Array
  • C# MenuStrip: Windows Forms
  • C# Modulo Operator: Get Remainder From Division
  • C# MonthCalendar Control: Windows Forms
  • C# Multiple Return Values
  • C# Multiply Numbers
  • C# namespace Keyword
  • C# NameValueCollection Usage
  • C# Nested Lists: Create 2D List or Jagged List
  • C# Nested Switch Statement
  • C# Environment.NewLine
  • C# Normalize, IsNormalized Methods
  • C# Null String Example
  • C# null Keyword
  • C# Nullable Examples
  • C# NullReferenceException and Null Parameter
  • C# Object Array
  • C# Obsolete Attribute
  • C# OfType Examples
  • C# OpenFileDialog Example
  • C# operator Keyword
  • C# Odd and Even Numbers
  • C# Bitwise Or
  • C# orderby Query Keyword
  • C# out Parameter
  • C# OutOfMemoryException
  • C# OverflowException
  • C# Overload Method
  • C# Override Method
  • C# PadRight and PadLeft: String Columns
  • C# Get Paragraph From HTML With Regex
  • C# Parallel.For Example (Benchmark)
  • C# Parallel.Invoke: Run Methods on Separate Threads
  • C# Parameter Optimization
  • C# Parameter Passing, ref and out
  • C# params Keyword
  • C# int.Parse: Convert Strings to Integers
  • C# partial Keyword
  • C# Path.ChangeExtension
  • C# Path Exists Example
  • C# Path.GetExtension: File Extension
  • C# Path.GetRandomFileName Method
  • C# Pragma Directive
  • C# Predicate (Lambda That Returns True or False)
  • C# Pretty Date Format (Hours or Minutes Ago)
  • C# PreviewKeyDown Event
  • C# Random Lowercase Letter
  • C# Random Paragraphs and Sentences
  • C# Random String
  • C# Random Number Examples
  • C# StreamReader ReadLine, ReadLineAsync Examples
  • C# readonly Keyword
  • C# Recursion Example
  • C# Regex, Read and Match File Lines
  • C# Regex Groups, Named Group Example
  • C# Regex.Matches Quote Example
  • C# Regex.Matches Method: foreach Match, Capture
  • C# Regex.Replace, Matching End of String
  • C# Regex.Replace, Remove Numbers From String
  • C# Regex.Replace, Merge Multiple Spaces
  • C# Regex.Replace Examples: MatchEvaluator
  • C# Regex.Split, Get Numbers From String
  • C# Regex.Split Examples
  • C# Regex Trim, Remove Start and End Spaces
  • C# RegexOptions.Compiled
  • C# RegexOptions.IgnoreCase Example
  • C# RegexOptions.Multiline
  • C# Region and endregion
  • C# Remove Char From String at Index
  • C# Remove Element
  • C# Remove HTML Tags
  • C# Remove String
  • C# Reserved Filenames
  • C# return Keyword
  • C# Reverse String
  • C# Reverse Words
  • C# Reverse Extension Method
  • C# RichTextBox Example
  • C# Right String Part
  • C# RNGCryptoServiceProvider Example
  • C# ROT13 Method, Char Lookup Table
  • C# SelectMany Example: LINQ
  • C# Sentinel Optimization
  • C# SequenceEqual Method (If Two Arrays Are Equal)
  • C# Shift Operators (Bitwise)
  • C# Short and ushort Types
  • C# Single Method: Get Element If Only One Matches
  • C# SingleOrDefault
  • C# Singleton Pattern Versus Static Class
  • C# Singleton Class
  • C# sizeof Keyword
  • C# Skip and SkipWhile Examples
  • C# Sleep Method (Pause)
  • C# Sort Dictionary: Keys and Values
  • C# Sort by File Size
  • C# Sort, Ignore Leading Chars
  • C# Sort KeyValuePair List: CompareTo
  • C# Sort Strings by Length
  • C# Thread.SpinWait Example
  • C# Math.Sqrt Method
  • C# stackalloc Operator
  • C# StackOverflowException
  • C# StartsWith and EndsWith String Methods
  • C# Static Array
  • C# Static Dictionary
  • C# Stopwatch Examples
  • C# Stream
  • C# StreamReader ReadToEnd Example (Read Entire File)
  • C# StreamReader ReadToEndAsync Example (Performance)
  • C# StreamReader Examples
  • C# StreamWriter Examples
  • C# String Append (Add Strings)
  • C# String Compare and CompareTo Methods
  • C# String Constructor (new string)
  • C# string.Copy Method
  • C# CopyTo String Method: Put Chars in Array
  • C# Empty String Examples
  • C# String Equals Examples
  • C# String For Loop, Count Spaces in String
  • C# string.Intern and IsInterned
  • C# String IsUpper, IsLower
  • C# String Length Property: Get Character Count
  • C# String Literal: Newline and Quote Examples
  • C# StringBuilder Capacity
  • C# StringBuilder Clear (Set Length to Zero)
  • C# StringBuilder Data Types
  • C# StringBuilder Performance
  • C# StringBuilder Equals (If Chars Are Equal)
  • C# StringBuilder Memory
  • C# StringBuilder ToString: Get String From StringBuilder
  • C# StringWriter Class
  • C# Sum Method: Add up All Numbers
  • C# Switch Char, Test Chars With Cases
  • C# Switch Enum
  • C# System (using System namespace)
  • C# Tag Property: Windows Forms
  • C# TextInfo Examples
  • C# TextReader, Returned by File.OpenText
  • C# TextWriter, Returned by File.CreateText
  • C# this Keyword
  • C# ThreadPool
  • C# Thread Join Method (Join Array of Threads)
  • C# ThreadPool.SetMinThreads Method
  • C# TimeZone Examples
  • C# Get Title From HTML With Regex
  • C# ToArray Extension Method
  • C# ToCharArray: Convert String to Array
  • C# ToDictionary Method
  • C# Token
  • C# ToList Extension Method
  • C# ToLookup Method (Get ILookup)
  • C# ToLower and ToUpper: Uppercase and Lowercase Strings
  • ToolStripContainer Control: Dock, Properties
  • C# ToolTip: Windows Forms
  • C# ToString Integer Optimization
  • C# ToString: Get String From Object
  • C# ToTitleCase Method
  • C# TrackBar: Windows Forms
  • C# Tree and Nodes Example: Directed Acyclic Word Graph
  • C# TreeView Tutorial
  • C# Trim Strings
  • C# Thread Methods
  • C# History
  • C# Features
  • C# Variables
  • C# Data Types
  • C# Operators
  • C# Keywords
  • C# New Features | C# Version Features
  • C# Programs
  • C# Program to swap numbers without third variable
  • C# Program to convert Decimal to Binary
  • C# Program to Convert Number in Characters
  • C# Program to Print Alphabet Triangle
  • C# Program to print Number Triangle
  • C# Program to generate Fibonacci Triangle
  • C# String Compare() method
  • C# String CompareOrdinal() method
  • C# String CompareTo() method
  • C# String Concat() method
  • C# String Contains() method
  • C# String CopyTo() method
  • C# String EndsWith() method
  • C# String Equals() method
  • C# String Format() method
  • C# String IndexOf() method
  • C# String Insert() method
  • C# String Intern(String str) method
  • C# String IsInterned() method
  • C# String Normalize() method
  • C# String IsNullOrEmpty() method
  • C# String IsNullOrWhiteSpace() method
  • C# String Join() method
  • C# String LastIndexOf() method
  • C# String LastIndexOfAny() method
  • C# String PadLeft() method
  • C# String PadRight() method
  • C# Nullable
  • C# String Remove() method
  • C# String Replace() method
  • C# String Split() method
  • C# String StartsWith() method
  • C# String SubString() method
  • C# Partial Types
  • C# Iterators
  • C# Delegate Covariance
  • C# Delegate Inference
  • C# Anonymous Types
  • C# Extension Methods
  • C# Query Expression
  • C# Partial Method
  • C# Implicitly Typed Local Variable
  • C# Object and Collection Initializer
  • C# Auto Implemented Properties
  • C# Dynamic Binding
  • C# Named and Optional Arguments
  • C# Asynchronous Methods
  • C# Caller Info Attributes
  • C# Using Static Directive
  • C# Exception Filters
  • C# Await in Catch Finally Blocks
  • C# Default Values for Getter Only Properties
  • C# Expression Bodied Members
  • C# Null Propagator
  • C# String Interpolation
  • C# nameof operator
  • C# Dictionary Initializer
  • C# Pattern Matching
  • C# Tuples
  • C# Deconstruction
  • C# Local Functions
  • C# Binary Literals
  • C# Ref Returns and Locals
  • C# Expression Bodied Constructors and Finalizers
  • C# Expression Bodied Getters and Setters
  • C# Async Main
  • C# Default Expression

Related Links

Adjectives
Ado
Ai
Android
Angular
Antonyms
Apache
Articles
Asp
Autocad
Automata
Aws
Azure
Basic
Binary
Bitcoin
Blockchain
C
Cassandra
Change
Coa
Computer
Control
Cpp
Create
Creating
C-Sharp
Cyber
Daa
Data
Dbms
Deletion
Devops
Difference
Discrete
Es6
Ethical
Examples
Features
Firebase
Flutter
Fs
Git
Go
Hbase
History
Hive
Hiveql
How
Html
Idioms
Insertion
Installing
Ios
Java
Joomla
Js
Kafka
Kali
Laravel
Logical
Machine
Matlab
Matrix
Mongodb
Mysql
One
Opencv
Oracle
Ordering
Os
Pandas
Php
Pig
Pl
Postgresql
Powershell
Prepositions
Program
Python
React
Ruby
Scala
Selecting
Selenium
Sentence
Seo
Sharepoint
Software
Spellings
Spotting
Spring
Sql
Sqlite
Sqoop
Svn
Swift
Synonyms
Talend
Testng
Types
Uml
Unity
Vbnet
Verbal
Webdriver
What
Wpf

Table of Contents

  • Introducing Interop.Word
  • Working with the Document
    • Find and Replace Text
    • Find and replace Bookmarks
    • Convert a DOC / DOCX file to PDF
    • Export a DOC / DOCX file into a PDF
  • From a Byte Array

If you’re working with ASP.NET C# and you need to open, edit or otherwise access a Microsoft Word DOC or DOCX file, you can easily do that using the Microsoft.Office.Interop.Word library package. This post explains how to do so: you might find it useful in case you need to perform such task or whenever you want to read some insights regarding the process.

Introducing Interop.Word

To access the namespace from your ASP.NET project you have two main choices:

  • Install the official Microsoft Office primary interop assemblies (PIAs) package on your machine by downloading and executing the runtime installer, then manually add a Reference to the Microsoft.Office.Interop.Word.dll file.
  • Install the appropriate NuGet package within Visual Studio using the Package Manager Console.

Needless to say, you should really go for the second option, but we’ll leave that to you.

Working with the Document

As soon as you have the namespace available, you can do the following:

// NS alias to avoid writing the required namespace all the time

using word = Microsoft.Office.Interop.Word;

// […]

Application app = new word.Application();

Document doc = app.Documents.Open(filePath);

Once you have the app and the doc objects you can perform a lot of editing task, such as:

Find and Replace Text

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

var textToFind = «any source text»;

var textToReplace = «any replacement text»;

var matchCase = true;

var matchWholeWord = true;

var matchWildcards = false;

var matchSoundsLike = false;

var matchAllWordForms = false;

var forward = true;

var wrap = 1;

var format = false;

var replace = 2;

app.Selection.Find.Execute(

    textToFind,

    matchCase,

    matchWholeWord,

    matchWildcards,

    matchSoundsLike,

    matchAllWordForms,

    forward,

    wrap,

    format,

    textToReplace,

    replace);

Find and replace Bookmarks

var bookmarkName = «anyName»;

var bookmarkNewValue = «anyValue»;

if (doc.Bookmarks.Exists(bookmarkName)) {

    doc.Bookmarks[bookmarkName].Select();

    app.Selection.TypeText(bookmarkNewValue);

}

Convert a DOC / DOCX file to PDF

Surprisingly enough, we can even do that with an one-liner thanks to the native «Save As PDF…» feature introduced with Office 2010.

doc.SaveAs2(«path-to-pdf-file.pdf», word.WdSaveFormat.wdFormatPDF);

Export a DOC / DOCX file into a PDF

This one is almost identical to the previous one in terms of results.

doc.ExportAsFixedFormat(tmpFile, WdExportFormat.wdExportFormatPDF);

… and so on.

For additional info regarding word-to-pdf conversion, you can also read this dedicated post: otherwise, keep reading.

What if you have the DOC or DOCX file stored outside the FileSystem, such as in blob-format within a Database? If that’s the case you need to use a temporary file, because most Office Interop methods do not support working with byte arrays, streams and so on.

Here’s a decent workaround you can use:

// byte[] fileBytes = getFileBytesFromDB();

var tmpFile = Path.GetTempFileName();

File.WriteAllBytes(tmpFile, fileBytes);

Application app = new word.Application();

Document doc = app.Documents.Open(filePath);

// .. do your stuff here …

doc.Close();

app.Quit();

byte[] newFileBytes = File.ReadAllBytes(tmpFile);

File.Delete(tmpFile);

You might notice that we used the

Close()

method in order to close (and thus save) the file. In case you wan’t to save your changes to the DOC / DOCX file you opened, you need to explicitly say it by adding the

WdSaveOptions.wdDoNotSaveChanges

object parameter in the following way:

doc.Close(word.WdSaveOptions.wdDoNotSaveChanges);

IMPORTANT: Do not underestimate the call to

app.Quit()

! If you don’t do that, the MS Word instance will be left open on your server (see this thread on StackOverflow for more info on that issue). If you want to be sure to avoid such dreadful scenario entirely you should strengthen the given implementation adding a try/catch fallback strategy such as the follow:

Application app = null;

Document doc = null;

try

{

    app = new word.Application();

    doc = Document doc = app.Documents.Open(filePath);

    // .. do your stuff here …

    doc.Close();

    app.Quit();

}

catch (Exception e)

{

    if (doc != null) doc.Close();

    if (app != null) app.Quit();

}

Unfortunately these objects don’t implement IDisposable, otherwise it would’ve been even easier.

That’s pretty much it: happy coding!

      Большинству операций, выполняемых программным способом, есть эквиваленты в пользовательском интерфейсе (UI), доступные как команды в меню и на панелях инструментов. Также существует нижележащая архитектура, обеспечивающая поддержку команд, выбираемых из UI. Всякий раз, когда вы создаете новый документ Word, он базируется на каком-либо шаблоне; расширение файлов шаблонов «.dot», а файлы документов – «.doc». Шаблон Word может содержать текст, код, стили, панели инструментов, элементы автотекста, комбинации клавиш для быстрого доступа к командам. Новый документ связывается с шаблоном и получает полный доступ к его элементам. Если вы не указываете конкретный шаблон, новый документ создается на основе стандартного шаблона «Normal.dot», который устанавливается при установке Word).

      Шаблон Normal.dot является глобальным, он доступен любому документу, который вы создаете. Вы могли бы при желании поместить весь свой код в Normal.dot и создавать все документы в своей среде на основе собственного шаблона Normal (Обычный). Но тогда его файл мог бы стать чрезмерно большим, поэтому более эффективное решение для многих разработчиков — создание собственных шаблонов для конкретных приложений. В документах, создаваемых на основе вашего шаблона, код из стандартного шаблона Normal по-прежнему будет доступен. При необходимости можно связывать документ с несколькими шаблонами в дополнение к шаблону Normal.

      Для работы с приложением Microsoft Word в .NET, используется объект Application, который является предком всех остальных объектов. Получив на него ссылку, вы можете работать с его методами и свойствами. Этот объект предоставляет большой набор методов и свойств, позволяющих программным путем управлять Microsoft Word. Код инициализации нового объекта Application, представлен ниже.

Microsoft.Office.Interop.Word.Application winword = 
  new Microsoft.Office.Interop.Word.Application();

Чтобы открыть существующий документ или создать новый, необходимо создать новый объект Document.

object missing = System.Reflection.Missing.Value;
Microsoft.Office.Interop.Word.Document document =
     winword.Documents.Add(ref missing, ref missing, ref missing, ref missing);

      Выполняя какую-либо операцию в пользовательском интерфейсе Word (например, добавляя верхний колонтитул), вы выделяете соответствующую секцию, используя объект «Selection», определяющий текущую позицию «невидимого» курсора и применяете к ней новый параметр форматирования с использованием объекта «Range». Данный объект представляет область в документе и может включать в себя все что угодно — от пары символов, до таблиц, закладок и много другого. Вы не ограничены одним объектом «Range» — в одном документе можно определить сразу несколько таких объектов.

//Добавление верхнего колонтитула
foreach (Microsoft.Office.Interop.Word.Section section in document.Sections)
{                   
    Microsoft.Office.Interop.Word.Range headerRange = 
    section.Headers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
    headerRange.Fields.Add(headerRange, Microsoft.Office.Interop.Word.WdFieldType.wdFieldPage);
    headerRange.ParagraphFormat.Alignment = 
    Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphCenter;
    headerRange.Font.ColorIndex = Microsoft.Office.Interop.Word.WdColorIndex.wdBlue;
    headerRange.Font.Size = 10;
    headerRange.Text = "Верхний колонтитул" + Environment.NewLine + "www.CSharpCoderR.com";
}

//Добавление нижнего колонтитула
foreach (Microsoft.Office.Interop.Word.Section wordSection in document.Sections)
{                   
    Microsoft.Office.Interop.Word.Range footerRange =
   wordSection.Footers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
    
    footerRange.Font.ColorIndex = Microsoft.Office.Interop.Word.WdColorIndex.wdDarkRed;
    footerRange.Font.Size = 10;
    footerRange.ParagraphFormat.Alignment = 
    Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphCenter;
    footerRange.Text = "Нижний колонтитул" + Environment.NewLine + "www.CSharpCoderR.com";
}

Чтобы добавить текст в документ, необходимо указать позицию для вставки и сам текст.

//Добавление текста в документ
document.Content.SetRange(0, 0);
document.Content.Text = "www.CSharpCoderR.com" + Environment.NewLine;

Так же вы можете применить к тексту определенный стиль.

//Добавление текста со стилем Заголовок 1
Microsoft.Office.Interop.Word.Paragraph para1 = document.Content.Paragraphs.Add(ref missing);
object styleHeading1 = "Заголовок 1";
para1.Range.set_Style(styleHeading1);
para1.Range.Text = "Исходники по языку программирования CSharp";
para1.Range.InsertParagraphAfter();

      В классе Microsoft.Office.Interop.Word.Document, присутствует коллекция «Tables», которая позволяет добавить таблицу в документ с использованием метода Add.

//Создание таблицы 5х5
Table firstTable = document.Tables.Add(para1.Range, 5, 5, ref missing, ref missing);

firstTable.Borders.Enable = 1;
foreach (Row row in firstTable.Rows)
{
    foreach (Cell cell in row.Cells)
    {
        //Заголовок таблицы
        if (cell.RowIndex == 1)
        {
            cell.Range.Text = "Колонка " + cell.ColumnIndex.ToString();
            cell.Range.Font.Bold = 1;
            //Задаем шрифт и размер текста
            cell.Range.Font.Name = "verdana";
            cell.Range.Font.Size = 10;                                                     
            cell.Shading.BackgroundPatternColor = WdColor.wdColorGray25;
            //Выравнивание текста в заголовках столбцов по центру
            cell.VerticalAlignment = 
                 WdCellVerticalAlignment.wdCellAlignVerticalCenter;
            cell.Range.ParagraphFormat.Alignment = 
                 WdParagraphAlignment.wdAlignParagraphCenter;
        }
        //Значения ячеек
        else
        {
            cell.Range.Text = (cell.RowIndex - 2 + cell.ColumnIndex).ToString();
        }
    }
}

       Для функционирования описанного выше кода, необходимо добавить к текущему проекту объектную библиотеку MS Word. Перейдите в меню «Проект» и выберете команду «Добавить ссылку» или в обозревателе решений, найдите пункт «Ссылки» и сделайте клик правой клавишей мыши по нему, из появившегося контекстного меню выберете соответствующий пункт.

      В открывшемся окне «Добавить ссылку», перейдите на вкладку «COM» и выберете «Microsoft Word 14.0 Object Library» из предложенного списка библиотек.

В обозревателе решений у вас появится ссылка на данную библиотеку.

      Перейдите в конструктор главной формы и добавьте из панели элементов, командную кнопку «Button». Данный элемент необходим для запуска процесса создания документа и вызов MS Word для отображения.

      Сделайте двойной клик левой клавишей мыши по элементу «Button» и вы перейдете в автоматически созданный метод события «button1_Click». Добавьте в него приведенный ниже код.

try
{               
    Microsoft.Office.Interop.Word.Application winword = 
        new Microsoft.Office.Interop.Word.Application();
    
    winword.Visible = false;

    //Заголовок документа
    winword.Documents.Application.Caption = "www.CSharpCoderR.com";

    object missing = System.Reflection.Missing.Value;

    //Создание нового документа
    Microsoft.Office.Interop.Word.Document document =
        winword.Documents.Add(ref missing, ref missing, ref missing, ref missing);

    //добавление новой страницы
    //winword.Selection.InsertNewPage();

    //Добавление верхнего колонтитула
    foreach (Microsoft.Office.Interop.Word.Section section in document.Sections)
    {                   
        Microsoft.Office.Interop.Word.Range headerRange = section.Headers[
        Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
        headerRange.Fields.Add(
       headerRange, Microsoft.Office.Interop.Word.WdFieldType.wdFieldPage);
        headerRange.ParagraphFormat.Alignment = 
       Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphCenter;
        headerRange.Font.ColorIndex = 
       Microsoft.Office.Interop.Word.WdColorIndex.wdBlue;
        headerRange.Font.Size = 10;
        headerRange.Text = "Верхний колонтитул" + Environment.NewLine + "www.CSharpCoderR.com";
    }

    //Добавление нижнего колонтитула
    foreach (Microsoft.Office.Interop.Word.Section wordSection in document.Sections)
    { 
        //
        Microsoft.Office.Interop.Word.Range footerRange =
wordSection.Footers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
        //Установка цвета текста
        footerRange.Font.ColorIndex = Microsoft.Office.Interop.Word.WdColorIndex.wdDarkRed;
        //Размер
        footerRange.Font.Size = 10;
        //Установка расположения по центру
        footerRange.ParagraphFormat.Alignment = 
            Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphCenter;
        //Установка текста для вывода в нижнем колонтитуле
        footerRange.Text = "Нижний колонтитул" + Environment.NewLine + "www.CSharpCoderR.com";
    }

    //Добавление текста в документ
    document.Content.SetRange(0, 0);
    document.Content.Text = "www.CSharpCoderR.com" + Environment.NewLine;

    //Добавление текста со стилем Заголовок 1
    Microsoft.Office.Interop.Word.Paragraph para1 = document.Content.Paragraphs.Add(ref missing);
    object styleHeading1 = "Заголовок 1";
    para1.Range.set_Style(styleHeading1);
    para1.Range.Text = "Исходники по языку программирования CSharp";
    para1.Range.InsertParagraphAfter();

    //Создание таблицы 5х5
    Table firstTable = document.Tables.Add(para1.Range, 5, 5, ref missing, ref missing);

    firstTable.Borders.Enable = 1;
    foreach (Row row in firstTable.Rows)
    {
        foreach (Cell cell in row.Cells)
        {
            //Заголовок таблицы
            if (cell.RowIndex == 1)
            {
                cell.Range.Text = "Колонка " + cell.ColumnIndex.ToString();
                cell.Range.Font.Bold = 1;
                //Задаем шрифт и размер текста
                cell.Range.Font.Name = "verdana";
                cell.Range.Font.Size = 10;                                                     
                cell.Shading.BackgroundPatternColor = WdColor.wdColorGray25;
                //Выравнивание текста в заголовках столбцов по центру
                cell.VerticalAlignment =
                WdCellVerticalAlignment.wdCellAlignVerticalCenter;
                cell.Range.ParagraphFormat.Alignment = 
                WdParagraphAlignment.wdAlignParagraphCenter;
            }
            //Значения ячеек
            else
            {
                cell.Range.Text = (cell.RowIndex - 2 + cell.ColumnIndex).ToString();
            }
        }
    }
    winword.Visible = true;
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message);
}

      Запустите ваш проект, нажав на клавиатуре, клавишу «F5». Нажмите на единственную кнопку, расположенную на главной форме вашего проекта. У вас автоматически откроется документ Microsoft Word с заполненным верхним и нижним колонтитулом, обычным и с применением стиля текстом, а так же заполненной таблицей.

      Для сохранения документа в определенной директории, добавьте приведенный ниже код, после строки «winword.Visible = true;».

//Сохранение документа
object filename = @"d:temp1.docx";
document.SaveAs(ref filename);
//Закрытие текущего документа
document.Close(ref missing, ref missing, ref missing);
document = null;
//Закрытие приложения Word
winword.Quit(ref missing, ref missing, ref missing);
winword = null;

      При работе с приложением Word в памяти компьютера создается отдельный процесс, если его не закрыть или не вывести созданный документ на экран, то он будет работать до выключения компьютера. Так же при постоянной работе с такими документами, их процессы будут накапливаться незаметно для пользователя, что может привести к зависанию компьютера или другим последствиям.

С выходом .NET5 дальнейшее развитие некоторых проектов оказалось под вопросом из-за сложности портирования. Если от небольших устаревших библиотек можно отказаться или найти им замену, то от зависимости Microsoft.Office.Interop.Word.dll очень сложно отказаться. Microsoft не планирует добавлять совместимость с .NET Core/5+, поэтому в этой статье мы рассмотрим, как создавать документы Word с помощью Open XML SDK.

Введение

Office Open XML, также известный как OpenXML или OOXML, представляет собой формат на основе XML для офисных документов, включая текстовые документы, электронные таблицы, презентации, а также диаграммы, фигуры и другой графический материал. В июне 2014 года Microsoft выпустила исходный код Open XML SDK на GitHub для работы с таким форматом.

У этой библиотеки есть серьёзные преимущества:

  • совместима с .NET 5+,

  • не требует установки Microsoft Office,

  • высокая скорость работы,

  • открытый исходный код.

Без минусов тоже не обошлось:

  • сложный API,

  • скудная документация.

Эти минусы определённо дополняют друг друга. Собственно, это и стало причиной создания этого материала.

А вот открытый исходный код является большим плюсом. Если бы код COM-библиотек был открыт, сообщество разработчиков помогло бы с портированием на .NET Core/5+. Кроме привлечения сторонних разработчиков, публичный код даёт каждому возможность находить и исправлять ошибки и уязвимости или хотя бы сообщать о них. Качество публичных библиотек очень важно для всех проектов, которые могут их использовать. Например, мы проводили небольшой аудит кода Open XML SDK при первом знакомстве с этой библиотекой.

Боль разработчиков Office

Для продуктов Office было разработано очень много софта сторонними разработчиками. Это плагины для Word, Excel, Outlook. Многие компании наделали себе удобных плагинов и генераторов отчётов в формате Word. А 3 июля 2021 года произошло страшное – все тикеты про поддержку .NET 5+ в VSTO / COM, разбросанные по разным ресурсам, были в одночасье закрыты с комментарием представителей Microsoft подобного рода:

…The VSTO/COM Add-Ins platform is very important to Microsoft, and we plan to continue to support it in Office with .NET Framework 4.8 as the last major version…VSTO/COM Add-Ins cannot be created with .NET Core and .NET 5+. This is because .NET Core/.NET 5+ cannot work together with .NET Framework in the same process and may lead to add-in load failures. Microsoft will not be updating VSTO or the COM Add-in platform to use .NET Core or .NET 5+…

По их информации, поддержка .NET 5+ не предвидится. Вот одна из таких дискуссий, которая ещё долго не прекращалась после этого объявления: «Please port Visual Studio Tools For Office (VSTO) to .NET 5/7, to enable VSTO add-in development in C# in .Net 5/7».

Если у разработчиков плагинов совсем всё плохо – им предложили перейти на Office JavaScript API (совсем другой язык, API не позволяет делать и малую часть того, что было), то для создания документов из C# кода можно попробовать перейти на библиотеку Open XML SDK (nuget).

Основы

Прежде чем приступить к разбору примеров, стоит понять, с чем вообще работают эти две библиотеки и в чём состоит разница их подходов.

Word документ — это набор запакованных xml-документов. Все элементы структурированы под тегами.

Например, параграф внутри документа будет выглядеть примерно вот так:

<w:p w:rsidR="007D2247" w:rsidRDefault="009A4B44"
         xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:r>
    <w:t>тест</w:t>
  </w:r>
  <w:bookmarkStart w:name="_GoBack" w:id="0" />
  <w:bookmarkEnd w:id="0" />
</w:p>

Сборка Interop.Word немного абстрагируется от этой структуры и часто работает с некоторым участком – Range – документа. А Open XML SDK идёт по пути отражения внутренней структуры документа в самом коде. Параграфы <w:p>, участки текста <w:t> и всё остальное становятся объектами в самом коде. Если вы не создадите тело документа, параграф и других обязательных «родителей», то и добавлять текст будет некуда.

На скриншоте как раз изображена внутренняя структура основного файла для документа Word – document.xml. Этот файл содержит само наполнение документа.

Скриншот сделан в очень нужной для работы с Open XML утилите Open XML SDK 2.5 Productivity Tool. К моменту написания статьи эта утилита была удалена с сайта Microsoft, а в репозитории Open-XML-SDK добавлена ссылка на некий DocxToSource, который должен стать заменой устаревшего Productivity Tool. Однако эта замена всё ещё является прототипом, поэтому пока лучше постараться найти старый добрый Productivity Tool. Старая утилита позволяет просмотреть строение документа, познакомиться с автогенерированным кодом.

Также она позволяет сравнить два разных документа (и код для их создания, и их внутреннее строение).

Примеры

Для Interop.Word во всей статье примем такой псевдоним для удобства чтения:

using MicrosoftWord = Microsoft.Office.Interop.Word;

Также для упрощения будем называть Open XML SDK просто Open XML.

Создание документа

Interop.Word:

MicrosoftWord.Application wordApp = new MicrosoftWord.Application();
MicrosoftWord.Document wordDoc = wordApp.Documents.Add();
MicrosoftWord.Range docRange = wordDoc.Range();
.... // тут при необходимости работаем с документом
wordDoc.SaveAs2(pathToDocFile);
wordApp.Quit();

Тут всё достаточно просто, но всё равно есть свои подводные камни. При работе с Interop мы взаимодействуем не просто с некоторым объектом в памяти, а с COM-объектом. Поэтому возникает необходимость завершать все процессы после окончания работы программы. Эта проблема не раз поднималась на Stack Overflow (1, 2), и ей предложено множество разных решений.

Есть решение с участием Marshal Class, являющимся частью InteropServices.

finally
{
  if (Marshal.IsComObject(wordDoc))
    try
    {
      Marshal.FinalReleaseComObject(wordDoc);
    }
    catch { throw; }
 
  if (Marshal.IsComObject(wordApp))
    try
    {
      Marshal.FinalReleaseComObject(wordApp);
    }
    catch { throw; }
}

Однако в таком случае можно упустить какие-нибудь процессы.

Есть более надёжный вариант с обращением к GC:

GC.Collect();
GC.WaitForPendingFinalizers();

Эти методы надо вызвать после того, как вся работа с COM-объектами будет завершена.

Если не завершить процессы, то при активном дебаге можно устроить себе такую ситуацию:

Но даже если в коде присутствует закрытие процессов после окончания работы, при прерывании программы вручную или её падении процесс останется запущенным. Такого недостатка нет при создании и работе с документом через Open XML.

Open XML:

using (WordprocessingDocument doc = 
         WordprocessingDocument.Create(pathToDocFile,
                                       WordprocessingDocumentType.Document,
                                       true))
{
  MainDocumentPart mainPart = doc.AddMainDocumentPart();
  mainPart.Document = new Document();
  Body body = mainPart.Document.AppendChild(new Body());
  SectionProperties props = new SectionProperties();
  body.AppendChild(props);
}

Обратите внимание на добавление SectionProperties, они понадобятся нам позже.

Добавление параграфа

Interop.Word

public static void InsertWordText(MicrosoftWord.Document doc,
                                      string text)
{
  MicrosoftWord.Paragraph paragraph = doc.Paragraphs.Add(Missing.Value);
  paragraph.Range.Text = text;
  paragraph.Range.InsertParagraphAfter();
}

Текст также можно сделать жирным или курсивным через параметр Font:

paragraph.Range.Font.Bold = 1;
paragraph.Range.Font.Italic = 1;

Изменить размер шрифта можно через:

paragraph.Range.Font.Size = 14;

Выравнивание текста выполняется через ParagraphFormat.Alignment:

paragraph.Range.ParagraphFormat.Alignment = MicrosoftWord.WdParagraphAlignment
                                                        .wdAlignParagraphCenter;

Open XML:

public static void AddText(WordprocessingDocument doc, string text)
{
  MainDocumentPart mainPart = doc.MainDocumentPart;
  Body body = mainPart.Document.Body;
  Paragraph paragraph = body.AppendChild(new Paragraph());

  Run run = paragraph.AppendChild(new Run());
  run.AppendChild(new Text(text));
  run.PrependChild(new RunProperties());
}

В случае с Open XML жирным или курсивным текст можно сделать через:

run.RunProperties.AddChild(new Bold());
run.RunProperties.AddChild(new Italic());

Изменение размера шрифта в этом случае немного неинтуитивно, но согласуется с общей логикой работы с Open XML:

run.RunProperties.AddChild(new FontSize(){ Val = "14"});

Выравнивание текста:

paragraph.ParagraphProperties.AddChild(new Justification()
                                       {
                                         Val = JustificationValues.Center
                                       });

Важно перед этим не забыть добавить к параграфу свойства:

paragraph.AppendChild(new ParagraphProperties());

Вставка заголовка

Предположим, что нам нужно вписать в документ заголовок. В случае Interop.Word нужно всего лишь небольшое дополнение к вставке текста, чтобы получить заголовок:

Interop.Word:

public static void InsertWordHeading1(MicrosoftWord.Document doc,
                                      string headingText)
{
  MicrosoftWord.Paragraph paragraph = doc.Paragraphs.Add(Missing.Value);
  paragraph.Range.Text = headingText;
  paragraph.Range.set_Style("Heading 1");
  paragraph.Range.InsertParagraphAfter();
}

В этом случае сначала задаём Range для записи нового текста и присваиваем ему стиль Heading 1.

Open XML:

public static void InsertWordHeading1(WordprocessingDocument doc,
                                      string headingText)
{
  MainDocumentPart mainPart = doc.MainDocumentPart;
  Paragraph para = mainPart.Document.Body.AppendChild(new Paragraph());
  Run run = para.AppendChild(new Run());
  run.AppendChild(new Text(headingText));
  para.ParagraphProperties = new ParagraphProperties(
                               new ParagraphStyleId() { Val = "Heading1" });
}

Тут, казалось бы, всё очень похоже. Аналогично добавляем параграф и в случае с Open XML организуем нужную иерархию объектов.

Однако на самом деле в случае с Open XML коварным оказывается добавление стиля. Interop.Word работает с реальным полноценным документом, как если бы вы запустили Word и нажали создать. А вот Open XML работает только с тем, что было создано. И если вы добавляете текст документу, созданному через Open XML, а не через Interop.Word, то в нём будут отсутствовать, например, стили. Соответственно, никакого стиля Heading1 в таком документе не будет. Его нужно сначала добавить.

Удобнее всего добавлять нужный стиль при создании документа. Есть два варианта: перенести стили из готового Word-документа или добавить эти стили вручную.

В первом случае в документе, из которого будет браться стиль, нужно обязательно применить искомый стиль. Сам перенос требует достаточно много кода, благо, в официальной документации есть мануал на эту тему.

Для второго варианта нам поможет Productivity Tool для Open XML, упоминавшийся ранее. Чтобы получить код, нужный для добавления желаемого стиля, создаём чистый документ Word, используем в нём нужный стиль и «скармливаем» этот документ утилите. Далее через использование кнопки Reflect Code на /word/styles.xml в структуре документа мы получим реализацию метода GeneratePartContent. В нём мы ищем реализацию нужного стиля и всё, что с ним связано, включая StyleParagraphProperties, StyleRunProperties и т.д.

Для стиля Heading 1 нужный нам автосгенерированный код будет выглядеть примерно так:

Style style2 = new Style() { Type = StyleValues.Paragraph,
                             StyleId = "Heading1" };
StyleName styleName2 = new StyleName(){ Val = "heading 1" };
....
style2.Append(styleRunProperties1);

Чтобы добавить перенесённый стиль к генерируемому документу, нужно создать набор стилей Styles и добавить стиль к набору. Далее к документу нужно добавить StyleDefinitionsPart и присвоить группу стилей. Выглядеть это будет вот так:

var styles = new Styles();
styles.Append(style2);
wordDocument.MainDocumentPart.AddNewPart<StyleDefinitionsPart>();
wordDocument.MainDocumentPart.StyleDefinitionsPart.Styles = styles;

У себя мы решили использовать вариант с шаблонным документом, чтобы в будущем при появлении необходимости в каком-либо стиле нужно было лишь использовать его в шаблоне и работать с ним в коде вместо того, чтобы каждый раз рыться в ProductivityTool и копировать себе полотна кода с объявлением нужного стиля.

Смена ориентации страницы

Для нашего отчёта нам нужна была именно ландшафтная ориентация страницы.

Interop.Word:

MicrosoftWord.Document wordDoc = wordApp.Documents.Add();
MicrosoftWord.Range docRange = wordDoc.Range();
docRange.PageSetup.Orientation = MicrosoftWord.WdOrientation
                                              .wdOrientLandscape;

У документа получаем нужный Range (страниц или всего документа) и задаём ландшафтную ориентацию.

Open XML:

var sectionProperties = mainPart.Document
                                .Body
                                .GetFirstChild<SectionProperties>();
sectionProperties.AddChild(new PageSize()
{
  Width = (UInt32Value)15840U,
  Height = (UInt32Value)12240U,
  Orient = PageOrientationValues.Landscape
});

C Open XML в этом случае всё не настолько абстрактно, как хотелось бы. Если вы инициализируете в PageSize только поле Orient, то ничего не изменится. Width и Height тоже нужно менять.

В дополнение к этому, ландшафтная ориентация обычно имеет другие размеры полей, поэтому, если у вас есть к ним требования, можно поправить их вот так:

sectionProperties.AddChild(new PageMargin()
{
  Top = 720,
  Right = Convert.ToUInt32(1440.0),
  Bottom = 360,
  Left = Convert.ToUInt32(1440.0),
  Header = (UInt32Value)450U,
  Footer = (UInt32Value)720U,
  Gutter = (UInt32Value)0U
});

Гиперссылки

Interop.Word:

public static void AddHyperlinkedText(MicrosoftWord.Document doc,
                                      string text,
                                      string url)
{
  MicrosoftWord.Range wrdRng = doc.Bookmarks
                                  .get_Item("\endofdoc")
                                  .Range;
  doc.Hyperlinks.Add(wrdRng, url, TextToDisplay: text);
}

Тут всё просто: как обычно, получаем нужный Range и добавляем гиперссылку. У метода Add много параметров, и можно сконструировать более сложную ссылку.

Open XML:

public static void AddHyperlinkedText(WordprocessingDocument doc,
                                      string text,
                                      string url)
{
  MainDocumentPart mainPart = doc.MainDocumentPart;
  Body body = mainPart.Document.Body;
  Paragraph paragraph = body.AppendChild(new Paragraph());

  var rel = mainPart.AddHyperlinkRelationship(new Uri(url), true);

  Hyperlink hyperlink = new Hyperlink(new Run(
                                    new RunProperties(
                                      new RunStyle 
                                      {
                                        Val = "Hyperlink",
                                      },
                                      new Underline
                                      {
                                        Val = UnderlineValues.Single
                                      },
                                      new Color
                                      {
                                        ThemeColor = ThemeColorValues.Hyperlink
                                      }),
                                      new Text
                                      {
                                        Text = text
                                      })) 
                    {
                      Id = rel.Id 
                    };

  paragraph.AppendChild(hyperlink);
}

Из существенных отличий тут то, что url нужно сначала обернуть в Uri и создать связь url с гиперссылкой через AddHyperlinkRelationship. Потом при создании самой гиперссылки, нужно присвоить полю Id новой гиперссылки Id созданной ранее связи.

Картинки

Interop.Word:

public static void InsertWordPicture(MicrosoftWord.Document doc,
                                     string picturePath)
{
  MicrosoftWord.Range wrdRng = doc.Bookmarks.get_Item("\endofdoc")
                                            .Range;
  wrdRng.InlineShapes.AddPicture(picturePath);
}

Тут всё достаточно просто, а с Open XML всё оказалось крайне сложно.

Open XML:

Для добавления картинки необходимо соблюсти сложную иерархию объектов с определёнными параметрами. Хорошо, что есть документация на этот счёт. Поэтому пропустим код, требуемый для добавления картинки в этой статье. Разберём ещё один момент, который почему-то не упоминается в документации. Можете заметить, что в том коде нигде не передаётся размер картинки. Фиксируется её размер тут:

new DW.Extent() { Cx = 990000L, Cy = 792000L }

и тут

new A.Extents() { Cx = 990000L, Cy = 792000L }

Если использовать этот код, то вместо нормальной картинки, будет вставлено малюсенькое изображение. Его реальный размер никак не изменится, но при отображении оно будет отмасштабировано до таких размеров:

Дело в том, что масштаб отображения картинки здесь завязан на такую вещь, как EMU (English Metric Units).

Для того чтобы передавать методу добавления картинки привычные размеры в пикселях, потребуется следующее преобразование:

double englishMetricUnitsPerInch = 914400;
double pixelsPerInch = 96;
double englishMetricUnitsPerPixel = englishMetricUnitsPerInch / pixelsPerInch;

double emuWidth = width * englishMetricUnitsPerPixel;
double emuHeight = height * englishMetricUnitsPerPixel;

Тут мы получаем количество EMU на пиксель, приняв значение PPI за 96, и умножаем полученное значение на нужное количество пикселей для ширины и высоты. В итоге у наc есть нужная нам ширина и высота в EMU. Их мы и передаём как Cx и Cy для Extent и Extents:

Cx = (Int64Value)emuWidth, Cy = (Int64Value)emuHeight

Таблицы

Interop.Word:

Генерация таблицы через Interop.Word достаточно прямолинейна. Разберём пример, как можно было бы вставить таблицу из квадратной матрицы строк.

public static void InsertWordTable(MicrosoftWord.Document doc,
                                   string[,] table)
{
  MicrosoftWord.Table oTable;
  MicrosoftWord.Range wrdRng = doc.Bookmarks
                                  .get_Item("\endofdoc")
                                  .Range;

  int rowCount = table.GetLength(0);
  int columnCount = table.GetLength(1);

  oTable = doc.Tables.Add(wrdRng,
                    rowCount,
                    columnCount,
                    DefaultTableBehavior: MicrosoftWord.WdDefaultTableBehavior
                                                       .wdWord9TableBehavior,
                    AutoFitBehavior: MicrosoftWord.WdAutoFitBehavior
                                                  .wdAutoFitWindow);

  for (int i = 0; i < rowCount; i++)
    for (int j = 0; j < columnCount; j++)
      oTable.Cell(i + 1, j + 1).Range.Text = table[i,j];
}

Параметры метода AddDefaultTableBehavior и AutoFitBehavior — как видно из их названия, отвечают за поведение таблицы при необходимости изменения размера под содержимое ячеек. Им присваиваются значения перечислений WdDefaultTableBehavior и WdAutoFitBehavior соответственно. Сам метод Add создаёт в документе таблицу с нужными нам параметрами.

Стиль к таблице можно применить следующим образом:

oTable.set_Style("Grid Table 4 - Accent 1");

Также для красивого выделения первого столбика, если он является заголовочным, можно присвоить true полю oTable.ApplyStyleFirstColumn.

Расстояние между параграфами текста контролируется через oTable.Range.ParagraphFormat.SpaceAfter. Для компактного отображения таблицы можно использовать

oTable.Range.ParagraphFormat.SpaceAfter = 0;

Также можно устанавливать тип написания текста к строкам или колонкам:

oTable.Rows[1].Range.Font.Bold = 1;
oTable.Column[1].Range.Font.Italic = 1;

Используя эти возможности, можно получить вот такую таблицу:

Open XML:

public static void InsertWordTable(WordprocessingDocument doc,
                                   string[,] table)
{
  DocumentFormat.OpenXml.Wordprocessing.Table dTable =
    new DocumentFormat.OpenXml.Wordprocessing.Table();

  TableProperties props = new TableProperties();

  dTable.AppendChild<TableProperties>(props);

  for (int i = 0; i < table.GetLength(0); i++)
  {
    var tr = new TableRow();

    for (int j = 0; j < table.GetLength(1); j++)
    {
      var tc = new TableCell();
      tc.Append(new Paragraph(new Run(new Text(table[i, j]))));

      tc.Append(new TableCellProperties());

      tr.Append(tc);
    }
    dTable.Append(tr);
  }
  doc.MainDocumentPart.Document.Body.Append(dTable);
}

При создании таблицы с нуля с Open XML стоит помнить о том, что никаких ячеек или строк к моменту ввода данных не существует. Их нужно сначала создать, соблюдая внутреннюю иерархию.

Поэтому при проходе по матрице мы для каждой строки создаём TableRow, а потом для каждого элемента в строке создаём TableCell, куда добавляем новые Paragraph, Run и Text с соответствующим значением из матрицы. TableCellProperties лучше также добавить сразу, чем потом при дальнейшей работе с таблицей наткнуться на System.NullReferenceException при попытке добавить свойство ячейке.

Если мы не зададим в TableProperties ни стиля, ни Borders, то таблица будет выглядеть вот так

Границы таблицы формируются через TableBorders.

var borderValues = new EnumValue<BorderValues>(BorderValues.Single);
var tableBorders = new TableBorders( 
                     new TopBorder { Val = borderValues, Size = 4 },
                     new BottomBorder {  Val = borderValues,  Size = 4 },
                     new LeftBorder { Val = borderValues, Size = 4 },
                     new RightBorder { Val = borderValues, Size = 4 },
                     new InsideHorizontalBorder { Val= borderValues, Size = 4 },
                     new InsideVerticalBorder { Val= borderValues, Size = 4 }));

Перечисление BorderValues здесь задаёт стиль границ.

TableBorders нужно добавить к TableProperties через

props.Append(tableBorders);

Границы таблицы можно не задавать, если ей будет присвоен какой-нибудь стиль. Главное не забыть, что стиль сначала нужно добавить к документу.

Задаётся стиль достаточно просто:

TableStyle tableStyle = new TableStyle()
                        {
                          Val = "GridTable4-Accent5"
                        };

Его так же, как и границы, нужно добавить к TableProperties:

props.Append(tableStyle);

Для того чтобы таблица заняла всю ширину страницы можно использовать TableWidth заданную следующим образом:

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

Значение 5000 тут взято «не из воздуха». Тип единицы ширины здесь мы задаём TableWidthUnitValues.Pct – единицы ширины в одну пятидесятую процента страницы или 0,02%. В итоге пять тысяч Pct это 100% ширины страницы.

Этот параметр добавляется к TableProperties аналогичным образом:

props.Append(tableWidth);

Важный момент: TableProperties должны быть добавлены к таблице до самих данных для того, чтобы они работали корректно. Их можно добавить и после других объектов, но тогда уже стоит использовать

dTable.PrependChild<TableProperties>(props);

Раскраска таблиц

Для формирования нашего отчёта нам нужно было раскрасить ячейки в некоторых таблицах документа.

Interop.Word:

oTable.Cell(i, j).Range.Shading.BackgroundPatternColor = MicrosoftWord.WdColor
                                                                    .wdColorRed;

где oTable – это созданная нами ранее таблица, i и j — это индексы нужной ячейки. Присваиваемое значение – перечисление WdColor.

Open XML:

tc.Append(new TableCellProperties(
            new Shading { Fill = "FF0000" }));

где tc – это TableCell, с которой идёт работа. Полю Fill присваивается строка с Hex-значением цвета.

Разрыв страницы

В нашем случае отчёт генерируется последовательно, поэтому разрыв страницы нужно было вставить после последнего добавленного текста.

Interop.Word:

public static void InsertWordBreak(MicrosoftWord.Document doc)
{
  MicrosoftWord.Range wrdRng = doc.Bookmarks.get_Item("\endofdoc")
                                            .Range;
  wrdRng.InsertBreak();
}

Open XML:

public static void InsertWordBreak(WordprocessingDocument doc)
{
  MainDocumentPart mainPart = doc.MainDocumentPart;
  mainPart.Document.Body.InsertAfter(new Paragraph(
                                       new Run(
                                         new Break()
                                         { 
                                           Type = BreakValues.Page
                                         })),
                                     mainPart.Document.Body.LastChild);
}

Тип разрыва меняется через перечисление BreakValues.

Footer/Header

Также нам нужны были футеры/хедеры в документе.

Interop.Word:

public static void InsertWordFooter(
  MicrosoftWord.Document doc,
  string headerText)
{
  MicrosoftWord.Range headerRange = doc.Sections
                                 .Last
                                 .Headers[MicrosoftWord.WdHeaderFooterIndex
                                                       .wdHeaderFooterPrimary]
                                 .Range;

  headerRange.Fields.Add(headerRange, MicrosoftWord.WdFieldType.wdFieldPage);
  headerRange.Text = headerText;
}

Через headerRange.Font можно поменять параметры текста, например размер, шрифт, цвет и т.д. А headerRange.ParagraphFormat.Alignment, как следует из названия, задаёт выравнивание текста. Это поле принимает значения WdParagraphAlignment.

Open XML:

Тут сложность состоит в том, что футер/хэдер сам по себе хранится в отдельном .xml файлике. Поэтому нам нужно связать хэдер/футер с содержанием документа через SectionProperties.

static void InsertWordHeader(HeaderPart part,
                             string headerText)
{
  MainDocumentPart mainPart = doc.MainDocumentPart;

  if (mainPart.HeaderParts.Any())
    return;

  HeaderPart headerPart = mainPart.AddNewPart<HeaderPart>();

  string headerPartId = mainPart.GetIdOfPart(headerPart);

  part.Header = new Header(
                  new Paragraph(
                    new ParagraphProperties(
                      new ParagraphStyleId() { Val = "Header" }),
                      new Run( new Text() { Text = headerText })));

  var sectionProperties = mainPart.Document
                                  .Body
                                  .GetFirstChild<SectionProperties>();
  sectionProperties.PrependChild<HeaderReference>(new HeaderReference()
                                                  {
                                                    Id = headerPartId
                                                  });
}

Если нужно, чтобы текст перезаписывался на новый при вызове метода добавления хедера, то вместо

if (mainPart.HeaderParts.Any())
  return;

можно использовать

mainDocumentPart.DeleteParts(mainDocumentPart.HeaderParts);

Для футера нужно будет передать mainDocumentPart.FooterParts.

Заключение

Описанные методы работы с Open XML SDK можно собрать в библиотеку классов для внутреннего использования в компании, что мы и сделали. Создание Word документов стало даже удобнее, чем было с Word Interop API.

Здесь может возникнуть закономерный вопрос, есть ли готовые библиотеки на основе Open XML SDK для упрощённой работы с документами? Ответ – однозначно да. Но, к сожалению, поддержка таких библиотек быстро прекращается. Истории создания таких проектов все одинаковые: программисты начинают работать с Word, осознают неудобство существующей инфраструктуры, дорабатывают её — и некоторые библиотеки публикуются на GitHub. Даже если удастся найти относительно свежую версию подобной библиотеки, то, скорее всего, она была реализована под задачи конкретного проекта, и в вашем проекте всё равно будет неудобной в использовании. Плюс появится риск остаться с неподдерживаемой библиотекой.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Victoria Khanieva, Svyatoslav Razmyslov. Tutorial: how to port a project from Interop Word API to Open XML SDK.

Понравилась статья? Поделить с друзьями:
  • By a change in the word itself
  • Buzz word of the day
  • Buzz type of word
  • Buying word for windows
  • Buying word for my laptop