Content Control Custom Events

Home Up Odds & Ends Photo Gallery Search Privacy Notice What's New? Ask a Word Expert Contact Me

 

 

The information in this website is provided without risk or obligation and free of charge.  However, if you have benefitted from my efforts here and would like to make a contribution to help me continue and maintain this work then any donation will be greatly appreciated. Please click the adjacent button to access PayPal.  Thank you.
 
This Microsoft Word Tips & Microsoft Word Help page demonstrates a method that you can use to create "custom" content control "On Enter, OnChange and OnExit" events using VBA procedures.  While the built-in Document_ContentControlOnEnter and Document_ContentControlOnExit events have improved with the release of Word2010, there remains a persistent bug in the Word2007 OnExit event.  Using this method you can avoid that bug and any text or changes that your document users make in content controls can be evaluated and processed in real time thus eliminating the need for the document user to first "exit" a content control. 

Before going any further I want to give some credit where credit is due.  First and foremost credit is due Jeff Vandervoort.  Jeff participates in the Microsoft Office Word for Developers forum and without his skills and his willingness to share the fruits of those skills this tips page would still be unpublished and gathering dust. 

Ever since content controls were introduced in Word 2007 I have wondered over :dunno, sought after :ipray:, and frequently groused :hairpull: about why Microsoft failed to provide a built-in "Document_ContentControlOnChange" event. While I was wondering and grousing, Jeff devised a relatively simple VBA procedure that continually monitors the active content control's range and when any change occurs triggers a call to another procedure that can evaluate and processes the change.

I would also like to mention and offer my appreciation to two regular contributors in the Microsoft Answers forum; "HansV" and Andreas Killer. The pseudonym is all I have in the case of HansV but both assisted in this effort with solutions to several nagging problems in the code. 

At the time of this writing Jeff's procedure is unpublished and part of a larger custom application that he is developing.  With Jeff's concurrence I have expanded the scope of his monitoring procedure to detect and trigger custom OnEnter and OnExit events.  Jeff says that his "baby sub-routine" is appears "all grown up" in the solution demonstrated here.  I say thank you Jeff for sharing your baby with me!!    
For the demonstration I will be using a Word 2010 document containing five "titled" and "tagged" content controls as shown in the following illustrations. "Name1, Name2, Age and Teachers" are plain text content controls.  "Grade" is a dropdown content control with "First, Second and Third" assigned as list entries.  All five content controls are locked and cannot be deleted.  The "Teachers" content control is locked for user editing.

:old: Note.  The illustrations in this tips page may or may not reflect that actual appearance or arrangement of the content controls contained in the demonstration document that you can download using the link at the end of this tips page.
The method uses the built-in Document_Open and Document_Close events to start and stop the monitor. These events are contained in the Word "ThisDocument" class module.  The code used in these events for this demonstration is shown in the scroll panel below:
Option Explicit
Private Sub Document_Close()
  Application.OnTime Now + TimeSerial(0, 0, 0.001), "Main.StopMonitoring"
End Sub
Private Sub Document_Open()
  Application.OnTime Now + TimeSerial(0, 0, 0.001), "Main.StartMonitoring"
End Sub
  
:old: Note -  You can add controls to the ribbon or QAT to start and stop the monitor.  The demonstration document has two controls added to the QAT as shown below.
The remaining variable declarations, procedures and functions are contained in a standard project module "Main."  The code used in the "Main" module for this demonstration is shown in the scroll panel below:
Option Explicit
Private bTabEntry As Boolean
Private oContentControl As ContentControl
Private bMonitoring As Boolean
Private pMonitoredText As String
Private bCheckboxChange As Boolean
Private b_Process_Entry_CC_Collection As Boolean 'Used to detect and process CC entry from _
          regular document text (i.e., click from document text into a CC range).
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Private Sub Document_ContentControlMonitor()
'Simulates "Change" event for ContentControls.
Dim oCC_Monitored As ContentControl
Dim bExit_CC_Collection As Boolean
'Set initial states
b_Process_Entry_CC_Collection = True
bExit_CC_Collection = False
bMonitoring = True
While bMonitoring
  'Call a function to determine the CC that has the focus if any.
  Set oContentControl = SelectedContentControl
  'If a CC has focus it is the one we will monitor.
  If Not oContentControl Is Nothing Then
    Set oCC_Monitored = oContentControl
    'Get its current text
    pMonitoredText = oCC_Monitored.Range.Text
    'This loops runs to detect changes in the monitored CC text or user actions that change the selection.
    Do While oCC_Monitored.Range.Text = pMonitoredText
      'Are we still in the monitored content control range?
      If Selection.InRange(oCC_Monitored.Range) Or _
                           oCC_Monitored.Range.End = Selection.Range.End Or _
                           oCC_Monitored.Range.Start = Selection.Range.Start Then
        'Did we just enter the monitored CC via "select/click" from any point external to the document CC collection?
        If b_Process_Entry_CC_Collection = True Then
          'If yes then trigger an OnEnter event.
          Call Custom_ContentControlOnEnter(oCC_Monitored)
          'Did we just enter a checkbox control?
          If oCC_Monitored.Type = 8 Then 'wdContentControlCheckBox. Use "8" so code runs in Word2007.
            DoEvents
            'Note: A "click" entry triggers automatic state change.
            If Not bTabEntry Then
              'Process the change.
              Call Custom_ContentControlOnChange(oCC_Monitored)
            End If
          End If
          b_Process_Entry_CC_Collection = False
        End If
      End If
      'Pause for air ;-)
      Sleep 1
      DoEvents
      'Stop the monitor if user closes documents or stops monitoring.
      If Not bMonitoring Then Exit Sub
      On Error GoTo Err_Handler
      'Check if user clicked or tabbbed out of the monitored CC during monitor loop?
      Set oContentControl = SelectedContentControl
      If Not oContentControl Is Nothing Then
        'See if user tabbed to or selected a different CC
        If oCC_Monitored.ID <> oContentControl.ID Then
          On Error GoTo 0
          'Since the user has moved to a different CC we trigger an OnExit\OnEnter event pair
          Call Custom_ContentControlOnExit(oCC_Monitored)
          Call Custom_ContentControlOnEnter(oContentControl)
          'Process automatic state change if user clicked in a checkbox CC.
          If oContentControl.Type = 8 Then 'wdContentControlCheckBox. Use "8" so code runs in Word2007
            DoEvents
            If Not bTabEntry Then
              Call Custom_ContentControlOnChange(oContentControl)
            End If
          End If
          'And begin monitoring the CC that now has the focus.
          Set oCC_Monitored = oContentControl
          pMonitoredText = oCC_Monitored.Range.Text
        End If
        bExit_CC_Collection = False
      Else
        'User has selected a text area in the document.
        'Flag exit from CC collection.
        bExit_CC_Collection = True
        Call Custom_ContentControlOnExit(oCC_Monitored)
        b_Process_Entry_CC_Collection = True
        Exit Do
      End If
    Loop
    'Process change.
    If Not bExit_CC_Collection Then
      Call Custom_ContentControlOnChange(oCC_Monitored)
    End If
    'Redefine the monitored text.
    pMonitoredText = oCC_Monitored.Range.Text
  End If
Err_ReEntry:
Wend
Exit Sub
Err_Handler:
Select Case Err.Number
  Case 5825 'User has deleted the monitored CC.
    Set oContentControl = Nothing
    'Return to monitor loop.
    Resume Err_ReEntry
  Case Else
    MsgBox Err.Number & " " & Err.Description
End Select
End Sub
'*********************************************************************************
Public Sub StartMonitoring()
  Document_ContentControlMonitor
End Sub
'These 2 subs allow procedures in other modules to control the event loop.
Public Sub StopMonitoring()
  bMonitoring = False
End Sub
Public Sub ResumeMonitoring()
  If bMonitoring = False Then Main.StartMonitoring
End Sub
'********************************THE EVENTS **************************************
Private Sub Custom_ContentControlOnChange(oCC_Changed As ContentControl)
Dim oCC_Target As ContentControl
Dim pText As String
Select Case oCC_Changed.Tag
  Case "Name1"
    Set oCC_Target = ActiveDocument.SelectContentControlsByTag("Name2").Item(1)
    With oCC_Target
      .LockContents = False
      .Range.Text = oCC_Changed.Range.Text
      .LockContents = True
    End With
  Case "Grade"
    Set oCC_Target = ActiveDocument.SelectContentControlsByTag("Teachers").Item(1)
    Select Case oCC_Changed.Range.Text
      Case "First"
        pText = "Mr. Creakle, Mrs. Magwitch, Mr. Murdstone, Miss Twemlow"
      Case "Second"
        pText = "Mr. Crowl, Mr. Gregsbury, Miss Pumblechook, Mr. Pyke, Miss Skiffins"
      Case "Third"
        pText = "Miss Crummles, Mrs. Podsnap, Miss Sliperskew, Mr. Squeers"
      Case Else
        pText = ""
    End Select
    With oCC_Target
      .LockContents = False
      .Range.Text = pText
      .LockContents = True
    End With
  Case "Age"
    With oCC_Changed
      If .ShowingPlaceholderText Then Exit Sub
      If Not IsNumeric(.Range.Text) Or Not Val(.Range.Text) > 0 Or Not Val(.Range.Text) < 115 Then
        If Len(.Range.Text) > 1 Then
          oCC_Changed.Range.Text = pMonitoredText
          MsgBox "Your entry must be a value between 1 and 114", vbInformation + vbOKOnly, "INVALID ENTRY"
          oCC_Changed.Range.Select
        ElseIf Len(.Range.Text) = 1 Then
          oCC_Changed.Range.Text = ""
          MsgBox "Your entry must be a value between 1 and 114", vbInformation + vbOKOnly, "INVALID ENTRY"
        Else
          oCC_Changed.Range.Text = ""
        End If
      End If
    End With
  Case "Checkbox1"
    Set oCC_Target = ActiveDocument.SelectContentControlsByTag("CheckBox1Clone").Item(1)
    #If VBA7 Then
      If oCC_Changed.Checked = True Then
        pText = "Thank you for choosing ..."
      Else
        pText = ""
      End If
    #Else
      pText = "This is a remnant of a Word 2010 checkbox content control that is not compatible with Word 2007."
    #End If
    With oCC_Target
      .LockContents = False
      .Range.Text = pText
      .LockContents = True
    End With
  Case "Linked1"
    Set oCC_Target = ActiveDocument.SelectContentControlsByTag("Linked2").Item(1)
    oCC_Target.Range.Text = oCC_Changed.Range.Text
  Case "Linked2"
    Set oCC_Target = ActiveDocument.SelectContentControlsByTag("Linked1").Item(1)
    oCC_Target.Range.Text = oCC_Changed.Range.Text
  Case Else
    Beep
End Select
Set oCC_Target = Nothing
End Sub

Sub Custom_ContentControlOnEnter(ByVal oCC_Entered As ContentControl)
Dim oCC_Target As ContentControl
Set oContentControl = oCC_Entered
b_Process_Entry_CC_Collection = False
Select Case oCC_Entered.Tag
  Case "Name1"
    Set oCC_Target = ActiveDocument.SelectContentControlsByTag("Name2").Item(1)
    With oCC_Target
      .LockContents = False
      If oCC_Target.ShowingPlaceholderText = True Then
        .Range.Text = "Active Clone"
      End If
      .Range.Shading.BackgroundPatternColor = wdColorPaleBlue
      .LockContents = True
    End With
  Case Else
    'Do nothing.
End Select
End Sub

Sub Custom_ContentControlOnExit(ByVal oCC_Exited As ContentControl)
Dim oCC_Target As ContentControl
Select Case oCC_Exited.Tag
  Case "Name1"
    Set oCC_Target = ActiveDocument.SelectContentControlsByTag("Name2").Item(1)
    If oCC_Exited.ShowingPlaceholderText Then
      oCC_Exited.Range.Shading.BackgroundPatternColor = wdColorRose
      With oCC_Target
        .LockContents = False
        .Range.Text = ""
        .Range.Shading.BackgroundPatternColor = wdColorAutomatic
        .LockContents = True
      End With
    Else
      oCC_Exited.Range.Shading.BackgroundPatternColor = wdColorAutomatic
      With oCC_Target
        .LockContents = False
        .Range.Shading.BackgroundPatternColor = wdColorAutomatic
        .LockContents = True
      End With
    End If
  Case Else
    'Do nothing.
End Select
End Sub
'******************************************** FUNCTIONS*********************************************
Private Function SelectedContentControl() As ContentControl
Dim oRng As Word.Range
Dim objCC As ContentControl
Dim oCCRichText As ContentControl
Dim oCCNested As ContentControl
Set oRng = Selection.Range.Paragraphs(1).Range
Set SelectedContentControl = Nothing
DoEvents
bTabEntry = True
If GetFocusMethod <> 1 Then bTabEntry = False
For Each objCC In oRng.ContentControls
  DoEvents
  If Selection.InRange(objCC.Range) Or _
               objCC.Range.End = Selection.Range.End Or _
               objCC.Range.Start = Selection.Range.Start Then
    If objCC.Type = wdContentControlRichText Then
      Set SelectedContentControl = objCC
      If objCC.Range.ContentControls.Count > 0 Then
        Set oCCRichText = objCC
          For Each oCCNested In oCCRichText.Range.ContentControls
            DoEvents
            If Selection.InRange(oCCNested.Range) Or _
               oCCNested.Range.End = Selection.Range.End Or _
               oCCNested.Range.Start = Selection.Range.Start Then
              Set SelectedContentControl = objCC
              Exit For
            End If
          Next oCCNested
      Else
        Set SelectedContentControl = objCC
        Exit For
      End If
    Else
      Set SelectedContentControl = objCC
      Exit For
    End If
  End If
  'If the CC tab is physically selected with the mouse.
  If Selection.Range.ContentControls.Count = 1 Then
    Set SelectedContentControl = Selection.Range.ContentControls(1)
    Exit For
  End If
  If Selection.Range.ContentControls.Count > 0 Then
    Set SelectedContentControl = Selection.Range.ContentControls(1)
    Exit For
  End If
Next objCC
End Function

Function GetFocusMethod() As Integer
DoEvents
'Determine how a control got the focus
If GetKeyState(vbKeyTab) < 0 Then
  If (GetKeyState(vbKeyShift) < 0) Then
    'Shift-Tab key is pressed
    GetFocusMethod = 4
  Else
    'Tab key is pressed
    GetFocusMethod = 1
  End If
ElseIf GetKeyState(vbKeyMenu) < 0 Then
  'Alt key is pressed (hotkey activation)
  GetFocusMethod = 2
ElseIf GetKeyState(vbKeyLButton) < 0 Then
  'Mouse left button is pressed
  GetFocusMethod = 3
  End If
End Function
:old: Notes.
1.  You can copy and paste the code from the scroll panels into your own document VBA project or download the complete demonstration document using the link provided at the bottom of this tips page.
2. The published code and illustrations in this tips page are limited to plain text and dropdown list type content controls.  Since first publishing this page I have incorporated a checkbox content control in the demonstration document.
With the content controls and required code in place you are ready to take advantage of real time monitoring and processing of your content control range text.

Enter the "Name" content control and type your name.  You will see the second "Name" content control display the same text in real time as you type.

Consider the benefit of real time notification of an invalid data entry. Using the "Age" control data is validated as you type.  It is impossible to enter invalid data or  save\print the document with invalid data entered!!

Data is inserted in real time as soon as the user makes a dropdown selection. 
You can download the complete demonstration document here:  Custom Content Controls Events Demo Document
For more on repeating data, validating data, and setting a display using content controls see my:  Repeating Data, Validating Content Control Entries and Insert Content with Content Controls.
That's it!  I hope you have found this tips page helpful and informative. 

Looking for something else?

Google
 

Ask an MS Word Expert Online

Ask a Question, Get an Answer ASAP.

JustAnswer