Search code examples
c#wpfmultithreadingclipboardword-interop

Threading a C# Word Interop method


I have a C#/WPF application that allows the user to export information into a Word document. At the moment it works - and creates the document as expected - however the UI locks and the moment I try to thread this method I get varying errors.

The document creation takes in a list of custom items, then builds sections in the Word document based on each item. It creates a table for each image, and in those tables I insert an image placeholder. Once this is done I traverse the document and replace the placeholders with their associated image.

I believe the threading issue is due to the way images are inserted into the document - utilising Clipboard.Clear() and Clipboard.SetDataObject(img).

Is there a cleaner way for me to insert JPG's from disk into the document, or is there a nice way to thread such a method? Here is the offending method:

private static void InsertImagesTables(string document, List<Record> allRecords)
    {
        Document oDoc = oWord.Documents.Open(document);
        Object oMissing = Missing.Value;
        object NormalStyle = "Normal";
        oWord.Visible = false;
        foreach (Record record in allRecords)
        {
            foreach (RecordImage rImage in record.Images)
            {
                //insert over placeholder
                var range = oDoc.Content;
                if (range.Find.Execute("[[" + record.Title + rImage.ImagePath + "]]"))
                {
                    try
                    {
                        //insert the image
                        var prevRange = range.Previous(WdUnits.wdCharacter);
                        Table imageTable;
                        imageTable = oDoc.Tables.Add(range, 1, 1, ref oMissing, ref oMissing);
                        imageTable.Borders.InsideLineStyle = WdLineStyle.wdLineStyleNone;
                        imageTable.Borders.OutsideLineStyle = WdLineStyle.wdLineStyleNone;

                        Image img = Image.FromFile(rImage.ImagePath + ".jpg");
                        Clipboard.Clear();
                        Clipboard.SetDataObject(img);
                        imageTable.Cell(1, 1).Range.Paste();
                        imageTable.Cell(1, 1).Range.set_Style(ref NormalStyle);
                        imageTable.Cell(1, 1).Range.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphCenter;

                        InlineShape inlineShape = imageTable.Cell(1, 1).Range.InlineShapes[1];

                        imageTable.Rows.Alignment = WdRowAlignment.wdAlignRowCenter;

                        string caption = rImage.Caption;
                        inlineShape.Range.InsertCaption(Label: "Figure", Title: " - " + caption, Position: WdCaptionPosition.wdCaptionPositionBelow);

                        range.Expand(WdUnits.wdParagraph);
                    }
                    catch // no image for record - do nothing
                    { }
                }
            }
        }

        oDoc.Close(true);
    }

I've tried BackgroundWorkers, Dispatchers, async Tasks and Threads (with and without ApartmentState.STA) with varying outcomes. Most just raise an error, but a few run and complete, without placing every image in the document - such as the STA approach.

Any help here is much appreciated,

Mike


Solution

  • Fixed. Thanks vernou for taking the time. I solved it by removing the Clipboard interaction and instead, properly utilising Word interop with a backgroundworker:

    Range cellRange = imageTable.Cell(1, 1).Range;
    cellRange.InlineShapes.AddPicture(rImage.ImagePath + ".jpg", ref oMissing, ref oMissing, ref oMissing);
    

    instead of:

    Clipboard.Clear();
    Clipboard.SetDataObject(img);
                            
    imageTable.Cell(1, 1).Range.Paste();