Updated:
Since some of the information in the original post was updated, I've updated this post.
You can give the following a try. On my computer, Option 1 takes about 8.5 secs. for 50 files (text in each file is 220 words), and Option 2 takes about 7.2 secs:
Add a reference to Microsoft.Word Object Library (ex: Microsoft.Word 16.0 Object Library)
- On menu, click Project
- Select Add Reference
- Select COM
- Select Microsoft.Word xx.x Object Library (ex: Microsoft.Word 16.0 Object Library)
Add the following using statements:
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Word = Microsoft.Office.Interop.Word;
using System.Threading;
using System.Threading.Tasks;
Create a class to hold the info needed to create the Word document.
Note: There are two options below. Both options use class "WordFileInfo".
WordFileInfo.cs:
public class WordFileInfo
{
public string Filename { get; set; }
public string LetterBody { get; set; }
public WordFileInfo()
{
}
public WordFileInfo(string filename, string letterBody)
{
this.Filename = filename;
this.LetterBody = letterBody;
}
}
In the two options below, "CreateWordDocument" is exactly the same in both. However, I've included it in each option, because it's part of "ClsHelperWord.cs".
Option 1:
*Note: This option uses both Task and Backgroundworker. If used with Windows forms, it helps to ensure that the UI remains responsive. Due to it's use of Backgroundworker, it may be slightly slower.
Create the "CurrentState" class which is used to pass data back from BackgroundWorker thread.
CurrentState.cs
public class CurrentState
{
public string Status { get; set; } = string.Empty;
public int PercentDone { get; set; } = 0;
}
ClsHelperWord.cs
public class ClsHelperWord : IDisposable
{
//depending on the number of files processed,
//changing this value may increase/decrease performance slightly
private int _maxConcurrentTasks = 25;
//create new instance
private Word.Application _wordApp = new Word.Application();
//store data that needs to be processed
public List<WordFileInfo> WordData { get; set; } = new List<WordFileInfo>();
public ClsHelperWord()
{
}
public ClsHelperWord(List<WordFileInfo> wordData)
{
//set value
this.WordData = wordData;
}
public void Close()
{
Dispose();
}
public void Dispose()
{
if (_wordApp != null)
{
//close Word
object oFalse = false;
_wordApp.Quit(ref oFalse, ref oFalse, ref oFalse);
//release all resources
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(_wordApp);
}
}
public void ConvertToWordDocument(System.ComponentModel.BackgroundWorker worker, System.ComponentModel.DoWorkEventArgs e)
{
DateTime lastReportedDateTime = DateTime.MinValue;
double percentDoneDbl = 0.0;
int percentDoneInt = 0;
//create new instance
CurrentState state = new CurrentState();
try
{
Debug.WriteLine("\nConverting to Word doc...");
using (SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(_maxConcurrentTasks))
{
List<Task> tasks = new List<Task>();
for (int i = 0; i < WordData.Count; i++)
{
concurrencySemaphore.Wait();
state.Status = "Processing file " + i + " of " + WordData.Count + "...";
Debug.WriteLine(state.Status);
//-------------------------------
// report progress
//-------------------------------
if (i % 5 == 0)
{
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
//ToDo: Do the following any time you want to
//update the status, progressBar, or any other
//variables you need updated during the
//background operation.
//Reporting progress too often will have a
//significant performance impact
percentDoneDbl = (Convert.ToDouble(i) / Convert.ToDouble(WordData.Count)) * 100.0;
percentDoneInt = Convert.ToInt32(percentDoneDbl);
state.PercentDone = percentDoneInt;
worker.ReportProgress(0, state); //report progress back to form
lastReportedDateTime = DateTime.Now; //update last reported time
}
//-------------------------------
// end of progress reporting
//-------------------------------
//create Word document(s)
var t1 = Task.Run(() =>
{
try
{
CreateWordDocument(WordData[i].Filename, WordData[i].LetterBody);
}
finally
{
concurrencySemaphore.Release();
}
}).ContinueWith(task =>
{
byte[] fBytes = File.ReadAllBytes(WordData[i].Filename);
return fBytes;
});
byte[] bytesRead = (byte[])t1.Result;
//System.Diagnostics.Debug.WriteLine("t1[" + i + "].Length: " + t1.Result.Length);
//add to list
tasks.Add(t1);
}
Task.WaitAll(tasks.ToArray());
//clean up
tasks.Clear();
}
Debug.WriteLine("Status: Complete " + DateTime.Now.ToString("HH:mm:ss"));
if (e.Cancel == true)
{
state.Status = "Status: Cancelled by user.";
}
else
{
state.PercentDone = 100;
state.Status = "Status: Complete.";
}
worker.ReportProgress(0, state); //report progress back to form
lastReportedDateTime = DateTime.Now; //update last reported time
}
finally
{
//don't put any catch statements here so
//the errors will end up in e.Error in
//backgroundWorker1_RunWorkerCompleted.
//Handle the errors in
//backgroundWorker1_RunWorkerCompleted.
}
}
private string CreateWordDocument(string filename, string userData)
{
//*Note: All indices start at 1.
string result = string.Empty;
//set value
object oMissing = System.Reflection.Missing.Value;
object oEndOfDoc = "\\endofdoc"; /* \endofdoc is a predefined bookmark */
Word.Documents documents = null;
Word.Document doc = null;
bool isVisible = false;
try
{
//suppress displaying alerts (such as prompting to overwrite existing file)
_wordApp.DisplayAlerts = Word.WdAlertLevel.wdAlertsNone;
//set Word visibility
_wordApp.Visible = isVisible;
//if writing/updating a large amount of data
//disable screen updating by setting value to false
//for better performance.
//re-enable when done writing/updating data, if desired
//_wordApp.ScreenUpdating = false;
if (_wordApp != null)
{
if (File.Exists(filename))
{
//System.Diagnostics.Debug.WriteLine("'" + filename + "' exists. Existing file will be modified.");
//open existing Word document
documents = _wordApp.Documents;
doc = documents.Open(filename, System.Reflection.Missing.Value, false, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, isVisible, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value);
doc.Activate();
}
else
{
//create new document
doc = _wordApp.Documents.Add();
doc.Activate();
//Debug.WriteLine("Created new document");
}
if (doc == null)
{
Debug.WriteLine("Error: doc is null");
return null;
}
//set text
doc.Content.Text = userData;
if (!_wordApp.ScreenUpdating)
{
//in case screen updating was previously disabled,
//enable screen updating by setting value to true
_wordApp.ScreenUpdating = true;
//refresh screen
//_wordApp.ScreenRefresh();
}
if (!String.IsNullOrEmpty(filename))
{
try
{
//Debug.WriteLine("Saving to '" + filename + "'");
//save the document
//doc.SaveAs(filename, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing);
doc.SaveAs2(filename, Word.WdSaveFormat.wdFormatXMLDocument, CompatibilityMode: Word.WdCompatibilityMode.wdWord2013);
}//try
catch (Exception ex)
{
string errMsg = "Error: WordWriteDocument - " + ex.Message;
System.Diagnostics.Debug.WriteLine(errMsg);
if (ex.Message.StartsWith("Cannot access read-only document"))
{
System.Windows.Forms.MessageBox.Show(ex.Message + "Please close the Word document, before trying again.", "Error - Saving", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
}
}
}
//set value
result = "Success";
}
}
catch (Exception ex)
{
string errMsg = "Error: WordWriteDocument - " + ex.Message;
System.Diagnostics.Debug.WriteLine(errMsg);
}
finally
{
if (doc != null)
{
//close document
doc.Close(Word.WdSaveOptions.wdDoNotSaveChanges, System.Reflection.Missing.Value, System.Reflection.Missing.Value);
//release all resources
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(doc);
}
}
return result;
}
To Use:
Add the following declarations (to a Windows form, UserControl, or Class). Alternatively, if using Windows forms, Backgroundworker can be added from the ToolBox.
private BackgroundWorker backgroundWorker1 = new BackgroundWorker();
private ClsHelperWord helperWord = null;
Set Backgroundworker properties and subscribe to events -- this can be placed in the constructor.
//set properties
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
//subscribe to events (add listener)
backgroundWorker1.DoWork += BackgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += BackgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerCompleted += BackgroundWorker1_RunWorkerCompleted;
BackgroundWorker1_DoWork
private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
System.ComponentModel.BackgroundWorker worker;
worker = (System.ComponentModel.BackgroundWorker)sender;
ClsHelperWord myClsHelperWord = (ClsHelperWord)e.Argument;
helperWord.ConvertToWordDocument(worker, e);
}
BackgroundWorker1_ProgressChanged
private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
CurrentState state = (CurrentState)e.UserState;
Debug.WriteLine(state.Status + " " + state.PercentDone.ToString());
}
BackgroundWorker1_RunWorkerCompleted
private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
// Handle the errors here.
// Write error message to console
// and/or a log file.
Debug.WriteLine("Error: " + e.Error.Message);
if (helperWord != null)
{
helperWord.Dispose();
helperWord = null;
}
}
else if (e.Cancelled)
{
Debug.WriteLine("Cancelled by user.");
if (helperWord != null)
{
helperWord.Dispose();
helperWord = null;
}
}
else
{
string finalStatus = "Status: Complete.";
Debug.WriteLine(finalStatus);
//clean up
if (helperWord != null)
{
helperWord.Dispose();
helperWord = null;
}
}
}
I use the following method to create some filenames and document text (for testing).
CreateTestData
private List<WordFileInfo> CreateTestData(string folderName, string sampleLetterBody)
{
//create data for testing
int numTestFiles = 50; //for testing purposes
List<WordFileInfo> wordData = new List<WordFileInfo>();
for (int i = 0; i < numTestFiles; i++)
{
string filename = System.IO.Path.Combine(folderName, "WordDoc" + (i + 1).ToString() + ".docx");
//add to list
wordData.Add(new WordFileInfo(filename, sampleLetterBody));
//Debug.WriteLine("Filename: '" + filename + "'");
}
return wordData;
}
Then use the following to test creating the Word document(s):
string folderName = @"C:\Temp";
string sampleLetterBody = "This is some text.";
//get data for testing
List<WordFileInfo> wordData = CreateTestData(folderName, sampleLetterBody);
if (helperWord != null)
{
helperWord.Dispose();
helperWord = null;
}
//create new instance and set property
helperWord = new ClsHelperWord(wordData);
if (backgroundWorker1.IsBusy != true)
{
backgroundWorker1.RunWorkerAsync(helperWord);
}//if
Option 2:
*Note: If used with Windows forms, this option may cause the UI to be unresponsive.
ClsHelperWord.cs
public class ClsHelperWord : IDisposable
{
//depending on the number of files processed,
//changing this value may increase/decrease performance slightly
private int _maxConcurrentTasks = 25;
//create new instance
private Word.Application _wordApp = new Word.Application();
//store data that needs to be processed
public List<WordFileInfo> WordData { get; set; } = new List<WordFileInfo>();
public ClsHelperWord()
{
}
public ClsHelperWord(List<WordFileInfo> wordData)
{
//set value
this.WordData = wordData;
}
public void Close()
{
Dispose();
}
public void Dispose()
{
if (_wordApp != null)
{
//close Word
object oFalse = false;
_wordApp.Quit(ref oFalse, ref oFalse, ref oFalse);
//release all resources
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(_wordApp);
}
}
public void ConvertToWordDocument()
{
Debug.WriteLine("\nConverting to Word doc...");
using (SemaphoreSlim concurrencySemaphore = new SemaphoreSlim (_maxConcurrentTasks))
{
List<Task> tasks = new List<Task>();
for (int i = 0; i < WordData.Count; i++)
{
concurrencySemaphore.Wait();
var t1 = Task.Run(() =>
{
try
{
CreateWordDocument(WordData[i].Filename, WordData[i].LetterBody);
}
finally
{
concurrencySemaphore.Release();
}
}).ContinueWith(task =>
{
byte[] fBytes = File.ReadAllBytes(WordData[i].Filename);
return fBytes;
});
byte[] bytesRead = (byte[])t1.Result;
//System.Diagnostics.Debug.WriteLine("t1[" + i + "].Length: " + t1.Result.Length);
//add to list
tasks.Add(t1);
}
Task.WaitAll(tasks.ToArray());
//clean up
tasks.Clear();
}
Debug.WriteLine("Status: Complete " + DateTime.Now.ToString("HH:mm:ss"));
}
private string CreateWordDocument(string filename, string userData)
{
//*Note: All indices start at 1.
string result = string.Empty;
//set value
object oMissing = System.Reflection.Missing.Value;
object oEndOfDoc = "\\endofdoc"; /* \endofdoc is a predefined bookmark */
Word.Documents documents = null;
Word.Document doc = null;
bool isVisible = false;
try
{
//suppress displaying alerts (such as prompting to overwrite existing file)
_wordApp.DisplayAlerts = Word.WdAlertLevel.wdAlertsNone;
//set Word visibility
_wordApp.Visible = isVisible;
//if writing/updating a large amount of data
//disable screen updating by setting value to false
//for better performance.
//re-enable when done writing/updating data, if desired
//_wordApp.ScreenUpdating = false;
if (_wordApp != null)
{
if (File.Exists(filename))
{
//System.Diagnostics.Debug.WriteLine("'" + filename + "' exists. Existing file will be modified.");
//open existing Word document
documents = _wordApp.Documents;
doc = documents.Open(filename, System.Reflection.Missing.Value, false, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, isVisible, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value);
doc.Activate();
}
else
{
//create new document
doc = _wordApp.Documents.Add();
doc.Activate();
//Debug.WriteLine("Created new document");
}
if (doc == null)
{
Debug.WriteLine("Error: doc is null");
return null;
}
//set text
doc.Content.Text = userData;
if (!_wordApp.ScreenUpdating)
{
//in case screen updating was previously disabled,
//enable screen updating by setting value to true
_wordApp.ScreenUpdating = true;
//refresh screen
//_wordApp.ScreenRefresh();
}
if (!String.IsNullOrEmpty(filename))
{
try
{
//Debug.WriteLine("Saving to '" + filename + "'");
//save the document
//doc.SaveAs(filename, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing);
doc.SaveAs2(filename, Word.WdSaveFormat.wdFormatXMLDocument, CompatibilityMode: Word.WdCompatibilityMode.wdWord2013);
}//try
catch (Exception ex)
{
string errMsg = "Error: WordWriteDocument - " + ex.Message;
System.Diagnostics.Debug.WriteLine(errMsg);
if (ex.Message.StartsWith("Cannot access read-only document"))
{
System.Windows.Forms.MessageBox.Show(ex.Message + "Please close the Word document, before trying again.", "Error - Saving", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
}
}
}
//set value
result = "Success";
}
}
catch (Exception ex)
{
string errMsg = "Error: WordWriteDocument - " + ex.Message;
System.Diagnostics.Debug.WriteLine(errMsg);
}
finally
{
if (doc != null)
{
//close document
doc.Close(Word.WdSaveOptions.wdDoNotSaveChanges, System.Reflection.Missing.Value, System.Reflection.Missing.Value);
//release all resources
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(doc);
}
}
return result;
}
}
To use:
I use the following method to create some filenames and document text (for testing).
CreateTestData
private List<WordFileInfo> CreateTestData(string folderName, string sampleLetterBody)
{
//create data for testing
int numTestFiles = 50; //for testing purposes
List<WordFileInfo> wordData = new List<WordFileInfo>();
for (int i = 0; i < numTestFiles; i++)
{
string filename = System.IO.Path.Combine(folderName, "WordDoc" + (i + 1).ToString() + ".docx");
//add to list
wordData.Add(new WordFileInfo(filename, sampleLetterBody));
//Debug.WriteLine("Filename: '" + filename + "'");
}
return wordData;
}
Then use the following to test creating the Word document(s):
string folderName = @"C:\Temp";
string sampleLetterBody = "This is some text.";
//get data for testing
List<WordFileInfo> wordData = CreateTestData(folderName, sampleLetterBody);
using (ClsHelperWord helperWord = new ClsHelperWord(wordData))
{
helperWord.ConvertToWordDocument();
}
The following posts may also be helpful.