Search code examples
c#.netms-wordvstooffice-addins

C# VSTO | Word crashes when attempting to access the properties of any Shape when ModernComments are enabled in Office365


I have a VSTO addin for Word, where one of its functions is to set CustomDocumentProperties and then refresh all Fields inside a Document.

Fields inside a Document can be in the main text of the Document but they can also be inside of a Shape. Using the Document.Fields property only gives you the collection of Fields in the main text of the Document, not Fields which are inside a Shape. So, to make sure that we get all the Fields, we check every Shape to see if there are any Fields in there.

This works fine in all versions of Word, except when ModernComments are enabled. When ModernComments are enabled and the code tries to access a Shape, Word completely crashes (and exits) and the User working in the Document loses all unsaved changes. I found two StackOverflow posts of other people who encountered similar behaviour:

  1. Accessing Word Shape.Type throws COMException (HRESULT: 0x80004005 E_FAIL)

  2. C# COMException reading property of MSWord Shape object Microsoft.Office.Interop.Word

In the second post, it is suggested to first do a Shape.Select() call before accessing any properties of the Shape. This does indeed prevent Word from crashing, but it has the side effect of navigating through the Document and selecting Shapes.

I've tried creating a Bookmark before iterating over all Shapes (and performing Shape.Select() on all of them) and then going back to the Bookmark, however, when a User is working inside a (Modern)Comments, then if I navigate to the Bookmark, the view switches to a different view.

Is this (Word crashing with ModernComments enabled while attempting to access a Shape) a known issue?

What would a solid solution be (for retrieving ALL Fields) with the above issue in mind?

I've also created a post for this issue on the Microsoft forums, here: https://answers.microsoft.com/en-us/msoffice/forum/all/c-vsto-word-crashes-when-attempting-to-access-the/88a33099-6859-43c4-b506-8c9d0e6afcf0


Solution

  • I solved the issue. To navigate back, use Range.Select(), but if you are in a (modern) comment, use Comment.Edit().

    See my code below:

    private void GetAllFieldsFromShapes(Word.Shapes shapes, ref List<Word.Field> fields)
        {
            if (fields == null) { fields = new List<Word.Field>(); }
    
            //We need to keep track of if we are inside of a modern comment.
            //The reason being that, performing a selection of a Shape, does indeed select the Shape in the graphical interface (Word/Excel, etc.)
            //which could introduce all kinds of problems and it just looks weird for the User...
            //We should make sure that the previously selected Shape does not stay selected. The way to do this is different depending on if we're in a (modern) comment or not.
            //If we're in a (modern) comment, then we need to enter the edit view of the comment.
            //If we're not in a (modern) comment, then we need to select the area which we had selected before selecting the Shape.
            var wordApp = Library.OfficeApplication as Word.Application;
            var selection = wordApp.Selection;
            var isInComment = selection.StoryType == Word.WdStoryType.wdCommentsStory;
            var range = selection.Range;
    
            try
            {
                foreach (var item in shapes)
                {
                    if (item is Word.Shape)
                    {
                        Word.Shape shape = null;
    
                        try
                        {
                            shape = item as Word.Shape;
    
                            //====
                            //I don't know why, but for some shapes, if you do not call 'Shape.Select()' then some of the underlying Properties are released and cannot be accessed.
                            //More info here: https://stackoverflow.com/questions/36043690/c-sharp-comexception-reading-property-of-msword-shape-object-microsoft-office-in
                            //and
                            //here: https://stackoverflow.com/questions/76226042/accessing-word-shape-type-throws-comexception-hresult-0x80004005-e-fail
                            shape.Select();
                            //
                            //However, it is important to note that, by performing this call, the shape is selected and we do actually need to perform an unselect action later on!
                            //
                            //====
    
                            var fieldsCount = 0;
                            //Get the Fields in the current Shape
                            try { fieldsCount = shape.TextFrame?.TextRange?.Fields?.Count ?? 0; }
                            catch { }
    
                            if (fieldsCount > 0)
                            {
                                foreach (Word.Field field in shape.TextFrame.TextRange.Fields)
                                {
                                    fields.Add(field);
                                }
                            }
                        }
                        finally
                        {
                            if (shape != null) { Marshal.ReleaseComObject(shape); }
                        }
                    }
                }
            }
            finally
            {
                //"Unselect" the selected shape
    
                if (range != null)
                {
                    if (isInComment == false)
                    {
                        //We can unselect the selected shape by selecting the area the cursor was before we selected the shape
                        try { range.Select(); }
                        catch { }
                    }
                    else
                    {
                        //If we try to select the area where the cursor was when we were in a (modern) comment, then a special window will popup which looks weird.                    
                        //So, to navigate to the place we were before, we need to open the edit window for the (modern) comment
    
                        if (range.Comments != null)
                        {
                            var comment = range.Comments[1];
                            if (comment != null)
                            {
                                //Open the edit window (which is actually still open, but will cause the window to gain focus again and
                                //which will navigate us back to the selected comment)
                                try { comment.Edit(); }
                                catch { }
                            }
                        }
                    }
                }
    
                if (range != null) { Marshal.ReleaseComObject(range); }
                if (selection != null) { Marshal.ReleaseComObject(selection); }
            }
        }