ICodeFactory Labs

Workaround for removing broken VSTO bookmark object after manual removal from the document

by Janko 12. July 2010 23:14

Bookmarks are very useful in developing and working with documents. Besides content controls, they are the main tool for our Word add-in project and are used as placeholders to mark items or locations in the Word document. Although developers would like bookmarks to have more options and operations like content controls, they are just not as flexible. Some options that could be supported by VSTO 3 but, unfortunately are not, could make our lives as developers easier:

  • before delete and after add events,
  • locking bookmark to prevent deleting,
  • locking bookmark and its content,
  • deleting bookmark with its content…

All these options are supported for content controls.

 

This post is about a problem that exists when user deletes a bookmark from the document manually. There is no event triggered when this happens. Deleting bookmarks manually causes its Interop.Word.Bookmark object to be removed, but Tools.Word.Bookmark object remains in the documents controls collection. We can access this remaining object but access to its properties and methods will throw exception with message "Object has been deleted". In this state it can not even be removed from the document.Controls collection. So if user removes the bookmark manually, our application will be unaware of that and will break when trying to access its properties, methods or even to remove it.

 

Fortunately, there is a way to “repair” this broken Tools.Word.Bookmark object enough for us be able to remove it from the collection of document’s VSTO objects. Then we are able to recover our application from this unwanted user action. Every time we want to get any bookmarks we use the following method:

 

Public Function GetBookmarkIfExists(ByVal bookmarkName As String) _ 
                                    As Tools.Word.Bookmark
        Dim returningControl As Tools.Word.Bookmark = Nothing
       
        ' If bookmark exists in word document, get it:
        If Me.Controls.Contains(bookmarkName) Then
            returningControl = Me.Controls(bookmarkName)

            ' If user has deleted bookmark manually, VSTO object exists but interop
            ' not. To recover from this unwanted state it is needed to remove VSTO
            ' object too:
            If (Not (Globals.ThisDocument.Bookmarks.Exists(bookmarkName))) Then
                Globals.ThisDocument.Bookmarks.Add(bookmarkName, Me.Range(0, 0))

                ' Remove all bindings from the bookmark and delete it:
                returningControl.DataBindings.Clear()
                returningControl.Delete()
              
                returningControl = Nothing
            End If
        End If
        Return returningControl 
End Function 

The method is in the document class.

 

This method checks if the corresponding Interop object exists for the VSTO object. If not, the VSTO object it broken. By adding interop object with the same name, VSTO object will be “repaired”. Range of repaired bookmark will not be as it was before removal but it is deletable now. Deleting bookmark will not delete its content.

 

Of course this method needs to be adapted to your code and application.

Currently rated 1.5 by 147 people

  • Currently 1.530612/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

VSTO | .NET

Workaround for VSTO bug: Content control Exiting event handler

by Janko 21. May 2010 02:04

If you have ever worked on some Word add-in where you use content controls, you maybe have encountered problems for which you have no explanations. This post is about one of the known VSTO issues and offers a workaround for it.

 

We were working on Word add-in that uses content controls. Also we needed to process data on entering and exiting a content control.

 

Good thing is that VSTO offers events Entering and Exiting.

Bad thing is that Exiting event has a bug. If you are doing some "simple" process in Exiting event handler it could work fine, but you never know what will happen. Using this event could produce very strange problems, problems which look like they should not have any connections with the event.

 

Several strange problems have occurred for which we were unable to find a solution. So we created different POC projects to reproduce and isolate the bugs and posted them on the Microsoft forum. Shortly the issues were confirmed - one of them as a new VSTO bug (https://connect.microsoft.com/VisualStudio/feedback/details/556456/programatically-deselecting-a-content-control-results-in-a-com-exception?wa=wsignin1.0) and another got recognized as a notorious VSTO bug. After using proposed workaround for notorious bug, both bugs were resolved.

 

The notorious problem manifests itself in following way:

In the document there are two building block content controls:

  1.  POCControl1 and
  2.  POCControl2

On the document startup we are adding Entering and Exiting event handlers to these controls.

-On Entering the control TrackChanges is set to TRUE and old value is remembered.

-On Exiting all changes are confirmed and TrackChanges is returned to the previous version.

Problem occurs in following scenario:

  1. Select POCControl1
  2. Entering event occurs.
  3. Unselect POCControl1.
  4. Exiting event occurs.
  5. PROBLEM: on line
    Me.TrackRevisions = Me._oldTrackRevisionsStatus
    Entering event for POCControl1 occurs (which should not happen). After it finishes, Exiting event continues to run from the specified line.

Even stranger behavior occurs when selecting POCControl1 and then selecting POCControl2. Here is the whole POC code:

 

Public Class ThisDocument

    Private _oldTrackRevisionsStatus As Boolean

    Private Sub ThisDocument_Startup(ByVal sender As Object, _
                                     ByVal e As System.EventArgs) Handles Me.Startup
        AddHandler POCControl1.Entering, AddressOf TextItemSelected
        AddHandler POCControl2.Entering, AddressOf TextItemSelected
        AddHandler POCControl1.Exiting, AddressOf TextItemUnselected
        AddHandler POCControl2.Exiting, AddressOf TextItemUnselected

        POCControl1.Tag = "UPPER control"
        POCControl2.Tag = "LOWER control"
    End Sub

    Public Sub TextItemSelected(ByVal sender As BuildingBlockGalleryContentControl, _
                                ByVal e As ContentControlEnteringEventArgs)
        MsgBox("Select " & sender.Tag & " beggining")

        ' Set track changes:
        _oldTrackRevisionsStatus = Me.TrackRevisions
        Me.TrackRevisions = True

        MsgBox("Select " & sender.Tag & " ending")
    End Sub

    Public Sub TextItemUnselected(ByVal sender As BuildingBlockGalleryContentControl, _
                           ByVal cevent As Tools.Word.ContentControlExitingEventArgs)
        MsgBox("Unselect " & sender.Tag & " beggining")

        If sender.Range.Revisions.Count > 0 Then
            sender.Range.Revisions.AcceptAll()
        End If

        Me.TrackRevisions = Me._oldTrackRevisionsStatus

        MsgBox("Unselect " & sender.Tag & " ending")
    End Sub
End Class

 

Proposed solution:

Do not use Exiting event at all. For example, use Application.WindowSelectionChange event instead, which we did. This event is triggered on every selection change in the active document window, whenever it is manually or programmatically caused. In its handler we are tracking "exiting" from the control.

 

So we removed Exiting event handler and related code from our POC project and added WindowSelectionChange event handler:

 

Private _oldTrackRevisionsStatus As Boolean
    Private selectedControl As Tools.Word.BuildingBlockGalleryContentControl

    Private Sub ThisDocument_Startup(ByVal sender As Object, _
                                     ByVal e As System.EventArgs) Handles Me.Startup
        AddHandler POCControl1.Entering, AddressOf TextItemSelected
        AddHandler POCControl2.Entering, AddressOf TextItemSelected
        
        POCControl1.Tag = "UPPERControl"
        POCControl2.Tag = "LOWERControl"
    End Sub

    Public Sub TextItemSelected(ByVal sender As BuildingBlockGalleryContentControl, _
                                ByVal e As ContentControlEnteringEventArgs)
        MsgBox("Select " & sender.Tag & " beggining")

        selectedControl = sender

        ' Set track changes:
        _oldTrackRevisionsStatus = Me.TrackRevisions
        Me.TrackRevisions = True

        MsgBox("Select " & sender.Tag & " ending")
    End Sub

    
    Public Sub WindowSelectionChange_Handler(ByVal sel As Interop.Word.Selection) _
                                      Handles ThisApplication.WindowSelectionChange
        Dim control As Interop.Word.ContentControl = sel.Range.ParentContentControl
        If selectedControl IsNot Nothing _
         AndAlso (control Is Nothing OrElse control.Tag <> selectedControl.Tag) Then
            MsgBox("Unselect " & selectedControl.Tag & " beggining")

            If selectedControl.Range.Revisions.Count > 0 Then
                selectedControl.Range.Revisions.AcceptAll()
            End If

            Me.TrackRevisions = Me._oldTrackRevisionsStatus

            selectedControl = Nothing

            MsgBox("Unselect ending")
        End If
    End Sub

 

If you have issues with content controls and you are using Exiting event, it is possible that it is the root of your problems. Use WindowSelectionChange event and adapt its handler for your own code. It helped us and might potentially help you get rid of some annoying bugs and unwanted behavior.

Currently rated 1.6 by 55 people

  • Currently 1.58182/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

ICF.Labs | .NET | VSTO


Contact

Development and Sales
ICodeFactory d.o.o.
Trg Marije Trandafil 24/2
21000 Novi Sad
Serbia, Europe
Phone: +381 (0)21 41 77 08
info[at]icodefactory[dot]com

Headquarter
T.C. Bagljaš, Lok. 11
23000 Zrenjanin
Serbia, Europe

Working hours
Monday - Friday
8am - 4pm (GMT+1)