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:
- POCControl1 and
- 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:
- Select POCControl1
- Entering event occurs.
- Unselect POCControl1.
- Exiting event occurs.
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.